# Webhooks

Signed HTTP calls to your server when emails are delivered, bounce, get replies and more.

## List webhook endpoints

`GET /projects/{project_id}/webhook-endpoints`

Every endpoint of the project, in one page.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `project_id` | path | string | Required |

```sh Request
curl "https://sendocado.com/api/v1/projects/$PROJECT_ID/webhook-endpoints" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY"
```

### Response

```json 200
{
  "webhook_endpoints": [
    {
      "id": "bofbcixgy29db8p5qa3e68f7e4qeqpno",
      "project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
      "url": "https://acme.example/webhooks/sendocado",
      "event_types": ["reply.received", "email.bounced"],
      "is_enabled": true,
      "created_at": 1789395200000
    }
  ],
  "next_cursor": null
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `project_not_found` | Project not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## Add a webhook endpoint

`POST /projects/{project_id}/webhook-endpoints`

An https URL on the public internet and the event types it gets. The response carries the signing secret; you can read it again later.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `project_id` | path | string | Required |

### Body

Send JSON with every field.

| Field | Type | Notes |
| --- | --- | --- |
| `url` | string | Required |
| `event_types` | ("email.sent" \| "email.delivered" \| "email.delivery_delayed" \| "email.bounced" \| "email.complained" \| "email.failed" \| "email.opened" \| "email.clicked" \| "reply.received" \| "contact.unsubscribed")[] | Required |

```sh Request
curl -X POST "https://sendocado.com/api/v1/projects/$PROJECT_ID/webhook-endpoints" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://acme.example/webhooks/sendocado",
  "event_types": ["reply.received", "email.bounced"]
}'
```

### Response

```json 201
{
  "id": "bofbcixgy29db8p5qa3e68f7e4qeqpno",
  "project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
  "url": "https://acme.example/webhooks/sendocado",
  "event_types": ["reply.received", "email.bounced"],
  "is_enabled": true,
  "created_at": 1789395200000,
  "secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 400 | `invalid_data` | Send a JSON request body. |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `project_not_found` | Project not found. |
| 409 | `conflict` | This campaign has enrollments, so it cannot be deleted. Pause it instead. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## Get a webhook endpoint

`GET /webhook-endpoints/{webhook_endpoint_id}`

is_enabled turns false when every attempt for many events in a row failed; turn it back on with an update.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `webhook_endpoint_id` | path | string | Required |

```sh Request
curl "https://sendocado.com/api/v1/webhook-endpoints/$WEBHOOK_ENDPOINT_ID" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY"
```

### Response

```json 200
{
  "id": "bofbcixgy29db8p5qa3e68f7e4qeqpno",
  "project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
  "url": "https://acme.example/webhooks/sendocado",
  "event_types": ["reply.received", "email.bounced"],
  "is_enabled": true,
  "created_at": 1789395200000
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `webhook_endpoint_not_found` | Webhook endpoint not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## Update a webhook endpoint

`PUT /webhook-endpoints/{webhook_endpoint_id}`

Send every field. is_enabled true turns back on an endpoint that was turned off.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `webhook_endpoint_id` | path | string | Required |

### Body

Send JSON with every field.

| Field | Type | Notes |
| --- | --- | --- |
| `url` | string | Required |
| `event_types` | ("email.sent" \| "email.delivered" \| "email.delivery_delayed" \| "email.bounced" \| "email.complained" \| "email.failed" \| "email.opened" \| "email.clicked" \| "reply.received" \| "contact.unsubscribed")[] | Required |
| `is_enabled` | boolean | Required |

```sh Request
curl -X PUT "https://sendocado.com/api/v1/webhook-endpoints/$WEBHOOK_ENDPOINT_ID" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://acme.example/webhooks/sendocado",
  "event_types": ["reply.received", "email.bounced"],
  "is_enabled": true
}'
```

### Response

```json 200
{
  "id": "bofbcixgy29db8p5qa3e68f7e4qeqpno",
  "project_id": "ujzde8gxd6ncf10epf91dhodzdoc9is0",
  "url": "https://acme.example/webhooks/sendocado",
  "event_types": ["reply.received", "email.bounced"],
  "is_enabled": true,
  "created_at": 1789395200000
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 400 | `invalid_data` | Send a JSON request body. |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `webhook_endpoint_not_found` | Webhook endpoint not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## Delete a webhook endpoint

`DELETE /webhook-endpoints/{webhook_endpoint_id}`

No more events are delivered to it.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `webhook_endpoint_id` | path | string | Required |

```sh Request
curl -X DELETE "https://sendocado.com/api/v1/webhook-endpoints/$WEBHOOK_ENDPOINT_ID" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY"
```

### Response

`204` with no body.

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `webhook_endpoint_not_found` | Webhook endpoint not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## Get the signing secret

`GET /webhook-endpoints/{webhook_endpoint_id}/secret`

The secret that signs every delivery to this endpoint.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `webhook_endpoint_id` | path | string | Required |

```sh Request
curl "https://sendocado.com/api/v1/webhook-endpoints/$WEBHOOK_ENDPOINT_ID/secret" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY"
```

### Response

```json 200
{
  "secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `webhook_endpoint_not_found` | Webhook endpoint not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## Rotate the signing secret

`POST /webhook-endpoints/{webhook_endpoint_id}/secret-rotations`

The new secret signs every attempt from now on, retries of earlier events included, so switch your receiver to it at once.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `webhook_endpoint_id` | path | string | Required |

```sh Request
curl -X POST "https://sendocado.com/api/v1/webhook-endpoints/$WEBHOOK_ENDPOINT_ID/secret-rotations" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY"
```

### Response

```json 201
{
  "secret": "whsec_3yX0aP8qLm2Vn7Bc4Rt9Wd6Kf1Hj5Gs0"
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `webhook_endpoint_not_found` | Webhook endpoint not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## List delivery attempts

`GET /webhook-endpoints/{webhook_endpoint_id}/deliveries`

Newest attempt first, with your server’s status code or the error. Kept for 30 days.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `webhook_endpoint_id` | path | string | Required |
| `cursor` | query | string \| null | Optional |
| `limit` | query | integer | Optional, default 50 |

```sh Request
curl "https://sendocado.com/api/v1/webhook-endpoints/$WEBHOOK_ENDPOINT_ID/deliveries" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY"
```

### Response

```json 200
{
  "webhook_deliveries": [
    {
      "id": "f4bs3e62rynnefj7qxi6rhxo55zbka52",
      "webhook_endpoint_id": "bofbcixgy29db8p5qa3e68f7e4qeqpno",
      "webhook_event_id": "35ye4scmejvqtia4d5rgn5s7s333h9mt",
      "event_type": "reply.received",
      "attempt": 1,
      "status_code": 200,
      "error": null,
      "duration_ms": 142,
      "created_at": 1790010800000
    }
  ],
  "next_cursor": null
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 400 | `invalid_data` | limit: Too big: expected number to be <=200 |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `webhook_endpoint_not_found` | Webhook endpoint not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## Deliver an event again

`POST /webhook-endpoints/{webhook_endpoint_id}/deliveries`

One more try, now (202); it shows in the delivery list like any other.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `webhook_endpoint_id` | path | string | Required |

### Body

Send JSON with every field.

| Field | Type | Notes |
| --- | --- | --- |
| `webhook_event_id` | string | Required |

```sh Request
curl -X POST "https://sendocado.com/api/v1/webhook-endpoints/$WEBHOOK_ENDPOINT_ID/deliveries" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "webhook_event_id": "35ye4scmejvqtia4d5rgn5s7s333h9mt"
}'
```

### Response

```json 202
{
  "webhook_event_id": "35ye4scmejvqtia4d5rgn5s7s333h9mt"
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 400 | `invalid_data` | Send a JSON request body. |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `webhook_endpoint_not_found` | Webhook endpoint not found. |
| 422 | `invalid_reference` | No template with that id in this project. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |
