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

# Webhooks

> Monitor incoming messages and statuses.

Using your Sendo dashboard, you can set up campaign-specific webhooks URLs to listen for incoming messages, message status updates, and [recipient optouts](/knowledge-base/keywords). These will be sent as `POST` requests to the URLs you provide, and they will be resent if the request fails.

If you navigate to the `Webhooks` tab on a `Campaign` page, you can see a history of the events that have been sent to your URLs and check for any failures.

<Info>As of February 7th, you can configure your webhooks to send events for a subset of the phone numbers attached to your campaign. This can be helpful if you have a testing number.</Info>

### Authentication

When you create a webhook URL on your dashboard, the system will generate a unique secret token. This token will be sent as a `X-Webhook-Token` header with each request. You should verify the token to ensure that the request is coming from Sendo.

### Retry policy

If our attempt to deliver the `POST` request to your webhook URL fails with an HTTP response that is not `2XX`, we will retry the request up to 6 times with a 60 second delay between each attempt. With each subsequent failure, the wait time will double. If after all attempts the webhook is still not acknowledged, no further attempts will be made.

<Warning>Please [contact us](mailto:support@sendo.dev) if you are experiencing issues receiving webhook events</Warning>

## `message.incoming`

Whenever a user sends a message to a phone number associated with a campaign, a `POST` request will be sent to your provided webhook URL with the following JSON body:

```json theme={null}
{
  "id": "04165fc8-2b3b-438e-8a33-3e5552c1122d",
  "type": "message.incoming",
  "timestamp": "2023-09-08T00:20:01.439Z",
  "message": {
    "id": "3106592b-ce0c-47e9-9a99-7511db1f8f16",
    "type": "MMS",
    "createdAt": "2023-09-08T00:20:00.849Z",
    "sentAt": "2023-09-08T00:19:56.025Z",
    "direction": "INBOUND",
    "from": "+1234567890",
    "to": "+14159436397",
    "body": "Hello world!",
    "status": "DELIVERED",
    "errorCode": null,
    "errorMessage": null,
    "mediaUrls": ["https://upload.wikimedia.org/wikipedia/commons/dog.jpg"],
    "campaignId": "j4tYqwn3"
  }
}
```

<Warning>Please [contact us](mailto:support@sendo.dev) if you think you have issues receiving incoming messages</Warning>

## `message.status`

Whenever the status of a sent message changes (see full list below), a `POST` request will be sent to your provided webhook URL with the following JSON body:

```json theme={null}
{
  "id": "a02d7de8-8405-47e8-acc5-6af147c67e24",
  "type": "message.status",
  "timestamp": "2023-09-08T00:20:01.439Z",
  "message": {
    "id": "34f9e131-6411-4d53-9fd5-5c75cc40778c",
    "type": "SMS",
    "createdAt": "2023-09-08T00:20:00.849Z",
    "sentAt": "2023-09-08T00:19:56.025Z",
    "direction": "OUTBOUND",
    "from": "+14159436397",
    "to": "+1234567890",
    "body": "Hello world!",
    "status": "DELIVERED",
    "errorCode": null,
    "errorMessage": null,
    "mediaUrls": [],
    "metadata": {
      "customId": "123456789"
    }
    "campaignId": "j4tYqwn3"
  }
}
```

<Tip>You can store custom metadata in the `metadata` field ([learn more](/api-reference/metadata))</Tip>

#### Statuses<br /><br />

| Status      | Description                          |
| ----------- | ------------------------------------ |
| `CREATED`   | The message has been created         |
| `QUEUED`    | The message is queued for sending    |
| `SCHEDULED` | The message is scheduled for sending |
| `SENT`      | The message has been sent            |
| `DELIVERED` | The message has been delivered       |
| `READ`      | The message has been opened          |
| `FAILED`    | The message send has failed          |
| `EXPIRED`   | The send has failed. Contact support |

<Note>See a full list of message errors [here](/api-reference/errors) for the `FAILED` status</Note>

<Tip>Often MMS messages will stay as `SENT` and are never marked as `DELIVERED`</Tip>

## `recipient.optout`

When a recipient texts `STOP` or another keyword to unsubscribe from messages, a `POST` request will be sent to your webhook URL with the following JSON body:

```json theme={null}
{
  "id": "9c78197b-d20c-42c3-9f7d-f04cf0fac3be",
  "type": "recipient.optout",
  "timestamp": "2023-09-08T00:20:01.439Z",
  "message": {
    "id": "c467664f-840c-498a-ab9f-eedd8d7e73e1",
    "type": "SMS",
    "createdAt": "2023-09-08T00:20:00.849Z",
    "sentAt": "2023-09-08T00:19:56.025Z",
    "direction": "INBOUND",
    "from": "+1234567890", // The number opting out
    "to": "+14159436397",
    "body": "STOP",
    "status": "DELIVERED",
    "errorCode": null,
    "errorMessage": null,
    "mediaUrls": [],
    "campaignId": "j4tYqwn3"
  }
}
```

<Note>These opt-out texts will also be sent as `message.incoming` events</Note>
