> ## Documentation Index
> Fetch the complete documentation index at: https://docs.membrane.agency/llms.txt
> Use this file to discover all available pages before exploring further.

# External Event Types

<Callout icon="🧪" theme="warning">
  External Event Types are an **experimental** workspace-level element. The shape of the API and console UI may change
  before it is generally available.
</Callout>

External Event Types let you subscribe to events from an external app without modifying a connector.

An External Event Type is a stand-alone workspace element with its own JavaScript implementation.
You define the event in your workspace, point it at an [Integration](/reference/workspace-elements/integrations)
or a specific [Connection](/reference/workspace-elements/connections), and Membrane runs the
`subscribe` / `handle` / `unsubscribe` lifecycle for subscriptions.

Use a stand-alone External Event Type when:

* The connector you use is shared (for example a public connector from the Membrane Universe)
  and you don't want to fork it just to add one event.
* Different tenants need different events from the same app and managing that variability inside
  the connector would be awkward.
* You want to iterate on event handling without going through a connector publish cycle.

<Callout icon="⚠️" theme="warning">
  To consume events emitted by an External Event Type, create an [External Event
  Subscription](/reference/workspace-elements/external-events/external-event-subscriptions) and point it at a webhook
  URI you control — see [Consuming events](#consuming-events) below.
</Callout>

## Scoping

A single External Event Type can live at one of three scopes:

| Scope           | Set when…                                   | Applies to                                                                                              |
| --------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| **Universal**   | No `integrationId` or `connectionId` is set | Every integration in the workspace. Children can be created per integration to override the definition. |
| **Integration** | `integrationId` is set                      | All connections of a single integration.                                                                |
| **Connection**  | `connectionId` is set                       | A single connection (typically used to override the type for one tenant).                               |

This mirrors how stand-alone [Actions](/reference/workspace-elements/actions) are scoped — the same
"universal → integration → connection" override chain applies.

## Definition

External Event Types are managed from the **External Event Types** page in the console (under the
experimental section), or via the `/external-event-types` API endpoints.

Each type has:

| Field              | Description                                                                                                                                       |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`             | Human-readable name shown in the console.                                                                                                         |
| `key`              | Stable identifier used in API calls and exports.                                                                                                  |
| `type`             | Implementation flavour. Currently only `webhook` is supported, so `subscribe`, `unsubscribe`, and `handle` must all be defined.                   |
| `parametersSchema` | JSON Schema for parameters the subscriber must supply (for example, `projectId`).                                                                 |
| `schema`           | JSON Schema describing the payload of emitted events.                                                                                             |
| `example`          | Optional example payload, used for testing and to generate `schema`.                                                                              |
| `subscribe`        | **Required.** JavaScript function called once when a subscription is created. Registers the webhook with the external system and returns `state`. |
| `handle`           | **Required.** JavaScript function called for each incoming webhook payload. Returns `{ events, response?, state? }`.                              |
| `unsubscribe`      | **Required.** JavaScript function called when the subscription is deleted. Deregisters the webhook.                                               |
| `refresh`          | Optional JavaScript function called periodically to keep the subscription alive when the external app requires it.                                |

The four function slots are the full event lifecycle for workspace External Event Types.

### Example implementation

A minimal end-to-end example for an external app that exposes a `/webhooks` REST endpoint. The
`subscribe` function registers the webhook and stashes the returned id; `handle` extracts events
from the incoming request; `unsubscribe` tears the registration down; `refresh` (optional) extends
the registration on a schedule.

```javascript subscribe.js theme={null}
export default async function subscribe({ apiClient, webhookUri, parameters }) {
  const { data } = await apiClient.post('/webhooks', {
    url: webhookUri,
    event: parameters.eventName,
  })

  return {
    state: { webhookId: data.id },
    // Optional — only set if the external app expires webhooks. Omit to disable refresh().
    nextRefreshTimestamp: Date.now() + 24 * 60 * 60 * 1000,
  }
}
```

```javascript handle.js theme={null}
export default async function handle({ data }) {
  // `data` is the parsed body of the incoming webhook request.
  return {
    events: [
      {
        type: data.action, // e.g. 'created', 'updated', 'deleted'
        record: data.payload,
      },
    ],
  }
}
```

```javascript unsubscribe.js theme={null}
export default async function unsubscribe({ apiClient, state }) {
  await apiClient.delete(`/webhooks/${state.webhookId}`)
}
```

```javascript refresh.js theme={null}
export default async function refresh({ apiClient, state }) {
  await apiClient.post(`/webhooks/${state.webhookId}/refresh`)
  return {
    nextRefreshTimestamp: Date.now() + 24 * 60 * 60 * 1000,
  }
}
```

See [Webhook Events](/reference/workspace-elements/external-events/webhook) for the full argument
and return shapes (signature verification, response shaping, state updates from `handle`, and so on).

## Consuming events

Stand-alone External Event Types are not wired to Flow triggers. Instead, you create an
[External Event Subscription](/reference/workspace-elements/external-events/external-event-subscriptions)
yourself and tell Membrane where to forward incoming events.

### 1. Create the subscription

`POST /external-event-subscriptions` with the connection and event type you want to subscribe to:

```bash theme={null}
curl -X POST https://api.getmembrane.com/external-event-subscriptions \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "connectionId": "651b…",
    "externalEventTypeId": "67ab…"
  }'
```

Membrane immediately runs the event type's `subscribe` function against the connection. The
response includes the new subscription's `id` and its initial `status` (`subscribed` on success).

### 2. Attach a webhook URI

Subscriptions created this way do **not** have a destination yet — incoming events are stored and
logged but go nowhere until you set `webhookUri`:

```bash theme={null}
curl -X PATCH https://api.getmembrane.com/external-event-subscriptions/{id} \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "webhookUri": "https://your-app.example.com/membrane/events"
  }'
```

From this point on, each event returned by the type's `handle` function is POSTed to
`webhookUri` with the event payload as the request body. The URI must be a public `http://` or
`https://` URL — Membrane rejects non-HTTP(S) schemes, loopback, link-local, and other private
addresses at delivery time.

You can change or clear the destination later by `PATCH`-ing `webhookUri` again (use `""` to
unset). To stop receiving events entirely, `DELETE` the subscription; Membrane will run the
event type's `unsubscribe` function before removing the record.

## Logs and Troubleshooting

Stand-alone External Event Types use the same monitoring surfaces as connector-level events:

* [External Webhooks](/docs/managing-membrane/monitoring-troubleshooting/logs/external-webhooks) — raw incoming payloads
* [External Events](/docs/managing-membrane/monitoring-troubleshooting/logs/external-events) — processed events and their status
* [External Event Subscriptions](/reference/workspace-elements/external-events/external-event-subscriptions) — subscription state and history
