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 |
curl "https://sendocado.com/api/v1/projects/$PROJECT_ID/webhook-endpoints" \
-H "Authorization: Bearer $SENDOCADO_API_KEY"{
"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; use null for a value you do not set.
| 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 |
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"]
}'{
"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 |
curl "https://sendocado.com/api/v1/webhook-endpoints/$WEBHOOK_ENDPOINT_ID" \
-H "Authorization: Bearer $SENDOCADO_API_KEY"{
"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; use null for a value you do not set.
| 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 |
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
}'{
"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 |
curl -X DELETE "https://sendocado.com/api/v1/webhook-endpoints/$WEBHOOK_ENDPOINT_ID" \
-H "Authorization: Bearer $SENDOCADO_API_KEY"Answers 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 |
curl "https://sendocado.com/api/v1/webhook-endpoints/$WEBHOOK_ENDPOINT_ID/secret" \
-H "Authorization: Bearer $SENDOCADO_API_KEY"{
"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 |
curl -X POST "https://sendocado.com/api/v1/webhook-endpoints/$WEBHOOK_ENDPOINT_ID/secret-rotations" \
-H "Authorization: Bearer $SENDOCADO_API_KEY"{
"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 |
curl "https://sendocado.com/api/v1/webhook-endpoints/$WEBHOOK_ENDPOINT_ID/deliveries" \
-H "Authorization: Bearer $SENDOCADO_API_KEY"{
"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; use null for a value you do not set.
| Field | Type | Notes |
|---|---|---|
| webhook_event_id | string | Required |
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"
}'{
"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. |