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

# Webhook event payloads

> Schemas, examples, and field semantics for every supported event type.

<Note>
  This is the beta webhook API, available in open beta. For the existing webhook system, see [Legacy webhooks](/mdx/guides/webhooks).
</Note>

Every webhook delivery uses the [common envelope](/mdx/beta/webhooks-overview#common-envelope) with the same `id`, `apiVersion`, `createdAt`, `type`, and `data` fields. The sections below document only the per-event `data` wrapper - the part that varies by event type. The `type` field on the envelope determines which schema below applies.

## Quick index

| Event type                                                | When it fires                                                                                                                                                         |
| --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`message.received`](#message-received)                   | An inbound SMS, MMS, or message was received by a Quo number.                                                                                                         |
| [`message.delivered`](#message-delivered)                 | An outbound message was delivered. Not a read receipt.                                                                                                                |
| [`call.ringing`](#call-ringing)                           | A call started ringing. Fires for incoming and outgoing. Context is narrower than other call events.                                                                  |
| [`call.answered`](#call-answered)                         | A call was connected. `answeredByUserId` identifies the Quo-side user associated with the answer when known. Outgoing calls fire this event for voicemail pickup too. |
| [`call.completed`](#call-completed)                       | A call ended. Terminal lifecycle event with the final `status` and `duration`.                                                                                        |
| [`call.forwarded`](#call-forwarded)                       | An incoming call was forwarded. Includes the forwarding phone numbers.                                                                                                |
| [`call.missed`](#call-missed)                             | An incoming call ended without being answered. Outgoing calls do not fire this event.                                                                                 |
| [`call.recording.completed`](#call-recording-completed)   | A call recording finished processing. May arrive after `call.completed`.                                                                                              |
| [`call.summary.completed`](#call-summary-completed)       | A call summary finished generating. May arrive long after the call ends.                                                                                              |
| [`call.transcript.completed`](#call-transcript-completed) | A call transcript finished processing. Order vs. summary is not guaranteed.                                                                                           |
| [`call.voicemail.completed`](#call-voicemail-completed)   | A voicemail was left and finished processing. Use `resource.callId` to correlate with the source call lifecycle.                                                      |
| [`contact.updated`](#contact-updated)                     | A contact was created or its fields changed.                                                                                                                          |
| [`contact.deleted`](#contact-deleted)                     | A contact was deleted. Resource shape matches `contact.updated`.                                                                                                      |

<Tip>
  To see any event payload against your endpoint without waiting for live traffic, call [`POST /webhooks/:id/events/test`](/mdx/beta/webhooks-api-reference#send-a-test-event) with the `eventType` you want to inspect.
</Tip>

## Field semantics

Two patterns appear in multiple events. Both indicate why a field may be empty rather than implying "no data exists".

### `contacts.lookupStatus`

Explains why `contacts.ids` may be empty:

| State         | Meaning                                                                                                                 |
| ------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `matched`     | Quo found one or more matching contacts. `ids` is populated.                                                            |
| `none`        | Quo checked for matching contacts and found none. `ids` is `[]`.                                                        |
| `unavailable` | Quo could not determine contact matches because the event lacked sufficient context. Treat `ids` as unknown, not empty. |

### `participants.resolution`

Explains whether participant context was resolved on call events:

| State         | Meaning                                                                                                  |
| ------------- | -------------------------------------------------------------------------------------------------------- |
| `available`   | Workspace and external participants are populated correctly.                                             |
| `unavailable` | Participant context could not be resolved. Treat empty arrays as unknown rather than as no participants. |

## Common type aliases

Several events reuse these types. Each event references them by name rather than redefining them inline.

```ts theme={null}
type MessageStatus =
  | 'queued' | 'sending' | 'sent' | 'delivered' | 'undelivered' | 'failed'
  | 'receiving' | 'received' | 'accepted' | 'scheduled' | 'read'
  | 'partially_delivered' | 'canceled'

interface MessageContext {
  phoneNumberId: string | null
  conversationId: string | null
  userId: string
  contacts: { ids: string[]; lookupStatus: 'matched' | 'none' | 'unavailable' }
  senderIdentifier: string
  recipientIdentifiers: string[]
}

interface CallContext {
  phoneNumberId: string | null
  conversationId: string | null
  phoneNumberType: 'shared' | 'private' | 'external' | null
  userId: string
  contacts: { ids: string[]; lookupStatus: 'matched' | 'none' | 'unavailable' }
  participants: {
    workspace: string[]
    external: string[]
    resolution: 'available' | 'unavailable'
  }
}

interface CallRingingContext {
  phoneNumberId: string | null
  conversationId: string | null
  userId: string
}

interface ContactContext {
  userId: string
  sharedWithIds: string[]
}
```

## `message.received` <a id="message-received" />

An inbound message was received by Quo. Use this as your inbound trigger.

```ts theme={null}
interface MessageReceivedEvent {
  type: 'message.received'
  data: {
    resource: {
      id: string
      direction: 'incoming'
      text: string
      status: MessageStatus
      createdAt: string
    }
    context: MessageContext
    links: { quo: string | null }
  }
}
```

```json theme={null}
{
  "data": {
    "resource": {
      "id": "AC-message",
      "direction": "incoming",
      "text": "hello",
      "status": "received",
      "createdAt": "2026-04-13T12:00:00.000Z"
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "senderIdentifier": "+15550001111",
      "recipientIdentifiers": ["+15550002222"]
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
```

`senderIdentifier` and `recipientIdentifiers` are raw participant identifiers. They are usually E.164 phone numbers, but direct-number and internal flows can emit non-phone identifiers.

## `message.delivered` <a id="message-delivered" />

An outbound message was delivered. This is delivery confirmation, not a read receipt.

```ts theme={null}
interface MessageDeliveredEvent {
  type: 'message.delivered'
  data: {
    resource: {
      id: string
      direction: 'outgoing'
      text: string
      status: MessageStatus
      createdAt: string
    }
    context: MessageContext
    links: { quo: string | null }
  }
}
```

```json theme={null}
{
  "data": {
    "resource": {
      "id": "AC-message",
      "direction": "outgoing",
      "text": "hello",
      "status": "delivered",
      "createdAt": "2026-04-13T12:00:00.000Z"
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "senderIdentifier": "+15550002222",
      "recipientIdentifiers": ["+15550001111"]
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
```

If you correlate deliveries with conversations, key on `context.conversationId` and `context.phoneNumberId` rather than `links.quo`.

## `call.ringing` <a id="call-ringing" />

`call.ringing` fires once at the start of an incoming or outgoing call. It is intentionally lightweight because call context is not fully available yet.

```ts theme={null}
interface CallRingingEvent {
  type: 'call.ringing'
  data: {
    resource: {
      id: string
      direction: 'incoming' | 'outgoing'
      createdAt: string
      updatedAt: string
    }
    context: CallRingingContext
    links: { quo: string | null }
  }
}
```

```json theme={null}
{
  "data": {
    "resource": {
      "id": "AC-call",
      "direction": "incoming",
      "createdAt": "2026-04-13T12:00:00.000Z",
      "updatedAt": "2026-04-13T12:00:00.000Z"
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "userId": "US123"
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
```

Later call events include richer context such as `contacts`, `participants`, and `phoneNumberType`.

## `call.answered` <a id="call-answered" />

A call was connected. `answeredByUserId` is the Quo user associated with the answer when known. It is not the external party who answered an outgoing call.

```ts theme={null}
interface CallAnsweredEvent {
  type: 'call.answered'
  data: {
    resource: {
      id: string
      direction: 'incoming' | 'outgoing'
      createdAt: string
      answeredAt: string | null
      answeredByUserId: string | null
      updatedAt: string | null
    }
    context: CallContext
    links: { quo: string | null }
  }
}
```

```json theme={null}
{
  "data": {
    "resource": {
      "id": "AC-call",
      "direction": "incoming",
      "createdAt": "2026-04-13T11:59:55.000Z",
      "answeredAt": "2026-04-13T12:00:00.000Z",
      "answeredByUserId": "US123",
      "updatedAt": "2026-04-13T12:00:00.000Z"
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
```

`call.answered` also fires on outgoing calls when the recipient's voicemail picks up. Treat this event as "the call connected" rather than "a person is on the line."

## `call.completed` <a id="call-completed" />

A call ended. This is the terminal lifecycle event with the final `status`, `duration`, and timestamps. Recording, summary, transcript, and voicemail artifacts (if any) arrive as separate events and may be delayed by minutes.

```ts theme={null}
interface CallCompletedEvent {
  type: 'call.completed'
  data: {
    resource: {
      id: string
      direction: 'incoming' | 'outgoing'
      status: CallCompletedStatus
      createdAt: string
      answeredAt: string | null
      completedAt: string | null
      updatedAt: string | null
      duration: number | null
      hasVoicemail: boolean
    }
    context: CallContext
    links: { quo: string | null }
  }
}

type CallCompletedStatus =
  | 'answered' | 'unanswered' | 'failed' | 'forwarded'
  | 'abandoned' | 'ai-handled' | 'unknown'
```

```json theme={null}
{
  "data": {
    "resource": {
      "id": "AC-call",
      "direction": "incoming",
      "status": "answered",
      "createdAt": "2026-04-13T11:59:55.000Z",
      "answeredAt": "2026-04-13T12:00:00.000Z",
      "completedAt": "2026-04-13T12:00:55.000Z",
      "updatedAt": "2026-04-13T12:00:55.000Z",
      "duration": 55,
      "hasVoicemail": false
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
```

Possible values for `status`:

| Value        | Meaning                                                                                                                |
| ------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `answered`   | The call connected. For outgoing calls, this may be either a person answering or the recipient's voicemail picking up. |
| `unanswered` | The call ended without being answered. May still have a voicemail — check `hasVoicemail`.                              |
| `failed`     | The call could not be placed or connected.                                                                             |
| `forwarded`  | The call was forwarded to another number.                                                                              |
| `abandoned`  | The caller hung up before the call was answered.                                                                       |
| `ai-handled` | An AI agent handled the call.                                                                                          |
| `unknown`    | Quo could not classify the final state.                                                                                |

* `hasVoicemail` indicates whether a voicemail was left. The voicemail itself arrives as a separate [`call.voicemail.completed`](#call-voicemail-completed) event.
* `duration` is in seconds and may be `null` for calls that never connected.

## `call.forwarded` <a id="call-forwarded" />

An incoming call was forwarded. This event carries the phone numbers involved in the forward. Other call lifecycle events do not include `forwardedFrom` or `forwardedTo`.

```ts theme={null}
interface CallForwardedEvent {
  type: 'call.forwarded'
  data: {
    resource: {
      id: string
      createdAt: string
      updatedAt: string | null
      forwardedFrom: string
      forwardedTo: string
    }
    context: CallContext
    links: { quo: string | null }
  }
}
```

```json theme={null}
{
  "data": {
    "resource": {
      "id": "AC-call",
      "createdAt": "2026-04-13T11:59:55.000Z",
      "updatedAt": "2026-04-13T12:00:00.000Z",
      "forwardedFrom": "+15550000001",
      "forwardedTo": "+15550000003"
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
```

## `call.missed` <a id="call-missed" />

An incoming call ended without being answered. Outgoing calls do not produce this event — they terminate with `call.completed` and a non-`answered` status.

The payload is intentionally minimal. To get the full call shape, look up the call by `resource.id` in the API.

```ts theme={null}
interface CallMissedEvent {
  type: 'call.missed'
  data: {
    resource: {
      id: string
      createdAt: string
      updatedAt: string
    }
    context: CallContext
    links: { quo: string | null }
  }
}
```

```json theme={null}
{
  "data": {
    "resource": {
      "id": "AC-call",
      "createdAt": "2026-04-13T11:59:55.000Z",
      "updatedAt": "2026-04-13T12:00:30.000Z"
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
```

## `call.recording.completed` <a id="call-recording-completed" />

A call recording finished processing. May arrive after `call.completed`. `recordings` is always an array — an empty `[]` means no recording metadata is available in this payload.

```ts theme={null}
interface CallRecordingCompletedEvent {
  type: 'call.recording.completed'
  data: {
    resource: {
      id: string
      direction: 'incoming' | 'outgoing'
      createdAt: string
      answeredAt: string | null
      completedAt: string | null
      updatedAt: string | null
      duration: number | null
      recordings: CallRecording[]
    }
    context: CallContext
    links: { quo: string | null }
  }
}

interface CallRecording {
  id: string | null
  duration: number | null
  startTime: string | null
  type: string | null
  url: string | null
}
```

```json theme={null}
{
  "data": {
    "resource": {
      "id": "AC-call",
      "direction": "incoming",
      "createdAt": "2026-04-13T11:59:55.000Z",
      "answeredAt": "2026-04-13T12:00:00.000Z",
      "completedAt": "2026-04-13T12:00:55.000Z",
      "updatedAt": "2026-04-13T12:01:05.000Z",
      "duration": 55,
      "recordings": [
        {
          "id": "REabc123",
          "duration": 55,
          "startTime": "2026-04-13T12:00:00.000Z",
          "type": "audio/mpeg",
          "url": "https://recordings.example.com/REabc123.mp3"
        }
      ]
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
```

Download or persist the recording file rather than relying on the URL for long-term access.

## `call.summary.completed` <a id="call-summary-completed" />

A call summary finished processing. This is a summary readiness event, not a call-ended event — the call may have ended much earlier. Trust `processingStatus`, not arrival time, when interpreting summary state.

```ts theme={null}
interface CallSummaryCompletedEvent {
  type: 'call.summary.completed'
  data: {
    resource: {
      callId: string
      processingStatus: 'absent' | 'in-progress' | 'completed' | 'failed'
      summary: string[] | null
      nextSteps: string[] | null
      fromPhoneNumber: string | null
      handledByAiAgent: boolean
      answeredByUserId: string | null
      jobs: AgentCallSummaryJob[]
    }
    context: CallContext
    links: { quo: string | null }
  }
}

interface AgentCallSummaryJob {
  icon: string
  name: string
  result: { data: Array<{ name: string; value: string | number | boolean }> }
}
```

```json theme={null}
{
  "data": {
    "resource": {
      "callId": "AC-summary",
      "processingStatus": "completed",
      "summary": ["Customer asked for pricing details."],
      "nextSteps": ["Send follow-up email."],
      "fromPhoneNumber": null,
      "handledByAiAgent": false,
      "answeredByUserId": null,
      "jobs": []
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
```

Notes:

* `summary` and `nextSteps` are arrays when `processingStatus === 'completed'`; otherwise `null`.
* `jobs` is an empty array when no AI agent job metadata is available.
* Use `callId` to correlate the summary with its source call and any transcript event.

## `call.transcript.completed` <a id="call-transcript-completed" />

A call transcript finished processing. Transcript and summary events are independent and may arrive in either order for the same call.

```ts theme={null}
interface CallTranscriptCompletedEvent {
  type: 'call.transcript.completed'
  data: {
    resource: {
      callId: string
      createdAt: string
      duration: number
      processingStatus: 'absent' | 'in-progress' | 'completed' | 'failed'
      dialogue: DialogueEntry[] | null
    }
    context: CallContext
    links: { quo: string | null }
  }
}

interface DialogueEntry {
  userId: string | null
  identifier: string | null
  content: string
  start: number
  end: number
}
```

```json theme={null}
{
  "data": {
    "resource": {
      "callId": "AC-transcript",
      "createdAt": "2026-04-13T12:00:01.000Z",
      "duration": 42,
      "processingStatus": "completed",
      "dialogue": [
        { "userId": "US123", "identifier": null, "content": "Thanks for calling, how can I help?", "start": 0, "end": 3 },
        { "userId": null, "identifier": "+15550000002", "content": "Hi, I wanted to ask about pricing.", "start": 3, "end": 7 }
      ]
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
```

Not every dialogue line maps to a Quo user. External participants surface as `identifier` without a `userId`; internal speakers surface as `userId` with or without an `identifier`.

## `call.voicemail.completed` <a id="call-voicemail-completed" />

A voicemail was left and finished processing. `resource.id` is the voicemail activity id. `resource.callId` is the source call activity id, or `null` if the source call could not be resolved. Use `resource.callId` to correlate the voicemail with the rest of the call lifecycle.

```ts theme={null}
interface CallVoicemailCompletedEvent {
  type: 'call.voicemail.completed'
  data: {
    resource: {
      id: string
      voicemailId: string | null
      callId: string | null
      direction: 'incoming' | 'outgoing'
      duration: number
      from: string
      to: string
      transcript: string | null
      recordingUrl: string | null
      createdAt: string
      updatedAt: string
    }
    context: CallContext
    links: { quo: string | null }
  }
}
```

```json theme={null}
{
  "data": {
    "resource": {
      "id": "AC-voicemail",
      "voicemailId": "VM123",
      "callId": "AC-source-call",
      "direction": "incoming",
      "duration": 18,
      "from": "+15550000002",
      "to": "+15550000001",
      "transcript": "Hi, leaving a quick message about the proposal...",
      "recordingUrl": "https://recordings.example.com/VM123.mp3",
      "createdAt": "2026-04-13T12:01:00.000Z",
      "updatedAt": "2026-04-13T12:01:10.000Z"
    },
    "context": {
      "phoneNumberId": "PN123",
      "conversationId": "CN123",
      "phoneNumberType": "shared",
      "userId": "US123",
      "contacts": { "ids": ["CT123"], "lookupStatus": "matched" },
      "participants": {
        "workspace": ["+15550000001"],
        "external": ["+15550000002"],
        "resolution": "available"
      }
    },
    "links": { "quo": "https://my.quo.com/inbox/..." }
  }
}
```

* `transcript` is `null` while processing or if transcription was not available for the voicemail.
* Download or persist the voicemail recording rather than relying on `recordingUrl` for long-term access.
* If the source call context cannot be fully resolved, `participants.resolution` may be `unavailable` and participant arrays may be empty. If the source call itself cannot be resolved, `resource.callId` will be `null` and `phoneNumberType` will be `null`.

## `contact.updated` <a id="contact-updated" />

A contact was created or its fields changed. Use `updatedAt` for ordering and freshness checks. Contact events are workspace-wide; see [Subscription rules](/mdx/beta/webhooks-overview#subscription-rules).

```ts theme={null}
interface ContactUpdatedEvent {
  type: 'contact.updated'
  data: {
    resource: ContactResource
    context: ContactContext
    links: { quo: string | null }
  }
}

interface ContactResource {
  id: string
  firstName: string | null
  lastName: string | null
  company: string | null
  role: string | null
  location: string | null
  source: string | null
  externalId: string | null
  emails: Array<{ value: string; type: 'email' }>
  phoneNumbers: Array<{ value: string; type: 'phone-number' }>
  customFields: CustomField[]
  createdAt: string
  updatedAt: string
}

type CustomField =
  | { name: string; key: string; id?: string; type: 'string' | 'url' | 'address'; value: string | null }
  | { name: string; key: string; id?: string; type: 'number'; value: number | null }
  | { name: string; key: string; id?: string; type: 'boolean'; value: boolean }
  | { name: string; key: string; id?: string; type: 'date'; value: string | null }
  | { name: string; key: string; id?: string; type: 'multi-select'; value: string[] }
```

```json theme={null}
{
  "data": {
    "resource": {
      "id": "CT123",
      "firstName": "Jane",
      "lastName": "Doe",
      "company": null,
      "role": null,
      "location": null,
      "source": null,
      "externalId": null,
      "emails": [{ "value": "jane@example.com", "type": "email" }],
      "phoneNumbers": [{ "value": "+15551234567", "type": "phone-number" }],
      "customFields": [
        { "name": "Department", "key": "department", "id": "i1", "type": "multi-select", "value": ["sales"] }
      ],
      "createdAt": "2026-01-01T00:00:00.000Z",
      "updatedAt": "2026-04-13T12:00:00.000Z"
    },
    "context": { "userId": "US123", "sharedWithIds": ["US456"] },
    "links": { "quo": "https://my.quo.com/contacts/CT123" }
  }
}
```

Notes:

* `customFields[].id` is omitted when the source item has no id.
* Invalid `number` or `date` custom field values normalize to `null`.
* `multi-select` values are always arrays.

## `contact.deleted` <a id="contact-deleted" />

A contact was deleted. The resource shape matches [`contact.updated`](#contact-updated); the event discriminator is `type`. Soft-deleted email addresses, phone numbers, and custom fields are removed from the payload rather than delivered with a deletion marker.

```ts theme={null}
interface ContactDeletedEvent {
  type: 'contact.deleted'
  data: {
    resource: ContactResource
    context: ContactContext
    links: { quo: string | null }
  }
}
```

The JSON shape is identical to `contact.updated`, with `type: "contact.deleted"` on the envelope.

## See also

* [Overview](/mdx/beta/webhooks-overview)
* [Webhook API reference](/mdx/beta/webhooks-api-reference)
* [Validate webhook signatures](/mdx/beta/webhooks-signature-validation)
* [Migrating from legacy](/mdx/beta/webhooks-differences-from-current)
