Webhooks & Integrations
Webhooks API
8 endpoints under https://api.mailneo.co/api/v2. Every request carries your key in the x-api-key header.
Scopes used on this page: webhooks:read, webhooks:write. Each endpoint below states the one it requires. Start with the quickstart →
/webhooksList webhook endpoints
Requires webhooks:read
Cursor-paginated. The signing secret is never included — it is returned once, in the response to the POST that created the endpoint, and cannot be read back afterwards. Endpoints created from the newsletter interface are not listed here.
Query parameters7
limitintegerdefault: 251..100cursorstring≤ 256 charsnext_cursor. Treat it as a black box: cursors are bound to the sort they were issued under and are validated strictly, so a hand-crafted, modified or foreign cursor — including any cursor issued before the 2026-08 format revision — answers 400 cursor_invalid. On that error, restart from the first page.order"asc" | "desc"default: descupdated_sincestring · date-timesort"updated_at" | "created_at"default: updated_atactive"true" | "false"eventstring≤ 64 charscurl "https://api.mailneo.co/api/v2/webhooks?limit=25&order=desc&sort=updated_at" \
-H "x-api-key: $MAILNEO_API_KEY"{
"data": [
{
"object": "webhook_endpoint",
"id": "c1yq8jdm1z0852l1y69bzo1yg",
"name": "Production endpoint",
"url": "https://example.com/hooks/mailneo",
"events": [
"contact.created"
],
"active": true,
"description": "Created via the API.",
"signature_version": "v1",
"api_version": "2026-08-15",
"header_names": [
"…"
],
"consecutive_failures": 0,
"last_success_at": "2026-06-24T15:45:00.000Z",
"last_failure_at": null,
"disabled_at": null,
"disabled_reason": null,
"created_at": "2026-05-11T09:30:00.000Z",
"updated_at": "2026-06-24T15:45:00.000Z"
}
],
"meta": {
"request_id": "bc0eeda2-bd0e-4f35-aa0e-ea7cbb0eec0f",
"has_more": false,
"next_cursor": null,
"synced_through": "2026-06-24T15:45:00.000Z"
}
}/webhooksRegister a webhook endpoint
Requires webhooks:write · Idempotency-Key optional
The response contains secret, and it is the only time it is ever returned. Store it before you do anything else: it is encrypted at rest and no endpoint on this API will return it again. Use it to verify the X-Mailneo-Signature header on every delivery.
The URL is validated at registration and must be publicly resolvable — an address inside a private range, a loopback address or a name that resolves to one is refused here rather than failing silently at delivery time. Subscribing to events that carry contact data (contact.*, event.tracked, suppression.added, unsubscribe.created) additionally requires contacts:read or suppressions:read on the key: a webhook delivers that data to an address you choose, so it may not grant a reach the key does not already have. Event names are validated against the catalog at GET /v2/webhooks/events; ["*"] subscribes to everything live today and is stored as that explicit list, so a later addition to the catalog does not silently change what you receive.
Query parameters1
dry_runbooleandata: null and a meta.impact block. Requires the resource's :read scope in addition to the scope this endpoint declares. Must be exactly true or false.Header parameters1
Idempotency-Keystring≤ 255 charsIdempotent-Replayed: true. Reusing a key with a different body is a 409 idempotency_key_reused. Keys are remembered for 24 hours.Body parameters5
urlstringrequired8..2048 charseventsstring[]required1..64 itemsnamestring1..200 charsdescriptionstring · nullable≤ 1000 charsheadersobjectcurl -X POST "https://api.mailneo.co/api/v2/webhooks" \
-H "x-api-key: $MAILNEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/mailneo",
"events": [
"contact.created"
],
"name": "Ada Lovelace",
"description": "Created via the API.",
"headers": {}
}'{
"data": {
"object": "webhook_endpoint",
"id": "c1yq8jdm1z0852l1y69bzo1yg",
"name": "Production endpoint",
"url": "https://example.com/hooks/mailneo",
"events": [
"contact.created"
],
"active": true,
"description": "Created via the API.",
"signature_version": "v1",
"api_version": "2026-08-15",
"header_names": [
"…"
],
"consecutive_failures": 0,
"last_success_at": "2026-06-24T15:45:00.000Z",
"last_failure_at": null,
"disabled_at": null,
"disabled_reason": null,
"created_at": "2026-05-11T09:30:00.000Z",
"updated_at": "2026-06-24T15:45:00.000Z",
"secret": "whsec_618cadf2628c…"
},
"meta": {
"request_id": "a2162537-a116-43a4-a416-285da31626ca",
"dry_run": false
}
}/webhooks/eventsList subscribable event types
Requires webhooks:read
Every event this API can deliver, with the name to put in an endpoint's events array. Discover the list rather than hard-coding it: an event name outside this catalog is rejected at registration, and the catalog grows. The response is complete — has_more is always false and there is no cursor, because the catalog is a small fixed set.
curl "https://api.mailneo.co/api/v2/webhooks/events" \
-H "x-api-key: $MAILNEO_API_KEY"{
"data": [
{
"object": "webhook_event_type",
"type": "…",
"description": "Created via the API.",
"object_type": "…"
}
],
"meta": {
"request_id": "e68ca16b-e58c-4fd8-a88c-a491e78ca2fe",
"has_more": false,
"next_cursor": null
}
}/webhooks/{webhookId}Retrieve a webhook endpoint
Requires webhooks:read
The signing secret is not included and cannot be retrieved. If you have lost it, delete this endpoint and register a new one.
Path parameters1
webhookIdstringrequired1..64 charscurl "https://api.mailneo.co/api/v2/webhooks/{webhookId}" \
-H "x-api-key: $MAILNEO_API_KEY"{
"data": {
"object": "webhook_endpoint",
"id": "c1yq8jdm1z0852l1y69bzo1yg",
"name": "Production endpoint",
"url": "https://example.com/hooks/mailneo",
"events": [
"contact.created"
],
"active": true,
"description": "Created via the API.",
"signature_version": "v1",
"api_version": "2026-08-15",
"header_names": [
"…"
],
"consecutive_failures": 0,
"last_success_at": "2026-06-24T15:45:00.000Z",
"last_failure_at": null,
"disabled_at": null,
"disabled_reason": null,
"created_at": "2026-05-11T09:30:00.000Z",
"updated_at": "2026-06-24T15:45:00.000Z"
},
"meta": {
"request_id": "066a2bef-056a-4a5c-a86a-2f15076a2d82"
}
}/webhooks/{webhookId}Update a webhook endpoint
Requires webhooks:write · Idempotency-Key optional
Merge-patch: omitted fields are left alone. events and headers are the exceptions — each REPLACES its stored value wholesale, because a subscription list you can only add to is one you can never leave. Send headers: {} to remove every custom header. A subscription to events that carry contact data — and repointing the url of an endpoint already subscribed to them — additionally requires contacts:read or suppressions:read, because a webhook delivers that data to an address you choose. A changed url is re-validated exactly as it was at registration. Setting active: true on an endpoint the platform switched off also clears its failure counter, so it is not disabled again by the next single failure. The signing secret is neither writable nor returned.
Path parameters1
webhookIdstringrequired1..64 charsQuery parameters1
dry_runbooleandata: null and a meta.impact block. Requires the resource's :read scope in addition to the scope this endpoint declares. Must be exactly true or false.Header parameters1
Idempotency-Keystring≤ 255 charsIdempotent-Replayed: true. Reusing a key with a different body is a 409 idempotency_key_reused. Keys are remembered for 24 hours.Body parameters6
urlstring8..2048 charseventsstring[]1..64 itemsnamestring1..200 charsdescriptionstring · nullable≤ 1000 charsheadersobjectactivebooleancurl -X PATCH "https://api.mailneo.co/api/v2/webhooks/{webhookId}" \
-H "x-api-key: $MAILNEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/mailneo",
"events": [
"contact.created"
],
"name": "Ada Lovelace",
"description": "Created via the API.",
"headers": {},
"active": true
}'{
"data": "…",
"meta": {
"request_id": "ecc5f1e4-edc5-4377-aec5-f50aefc5f69d",
"dry_run": false
}
}/webhooks/{webhookId}Delete a webhook endpoint
Requires webhooks:write
Permanent. Deliveries stop immediately and the endpoint's delivery log is removed with it. Re-registering gives you a NEW signing secret, so a consumer verifying signatures must be updated. Call with ?dry_run=true first if you are not certain. To stop deliveries reversibly, PATCH with active: false instead.
Path parameters1
webhookIdstringrequired1..64 charsQuery parameters1
dry_runbooleandata: null and a meta.impact block. Requires the resource's :read scope in addition to the scope this endpoint declares. Must be exactly true or false.curl -X DELETE "https://api.mailneo.co/api/v2/webhooks/{webhookId}" \
-H "x-api-key: $MAILNEO_API_KEY"{
"data": {
"object": "webhook_endpoint",
"id": "c1yq8jdm1z0852l1y69bzo1yg",
"deleted": true
},
"meta": {
"request_id": "aca89c76-ada8-4e09-aaa8-9950aba89ae3",
"dry_run": false
}
}/webhooks/{webhookId}/deliveriesList an endpoint's deliveries
Requires webhooks:read
The delivery log for one endpoint, newest first, cursor-paginated. There is no updated_since: the log has no index supporting an incremental poll, and advertising a watermark that cannot be honoured would make a sync client silently miss rows forever. The event payload is not included — fetch the object from its own endpoint using event_id for correlation.
Path parameters1
webhookIdstringrequired1..64 charsQuery parameters4
limitintegerdefault: 251..100cursorstring≤ 256 charsnext_cursor. Treat it as a black box: cursors are bound to the sort they were issued under and are validated strictly, so a hand-crafted, modified or foreign cursor — including any cursor issued before the 2026-08 format revision — answers 400 cursor_invalid. On that error, restart from the first page.order"asc" | "desc"default: descstatus"PENDING" | "SUCCEEDED" | "FAILED" | "RETRYING" | "BLOCKED"curl "https://api.mailneo.co/api/v2/webhooks/{webhookId}/deliveries?limit=25&order=desc" \
-H "x-api-key: $MAILNEO_API_KEY"{
"data": [
{
"object": "webhook_delivery",
"id": "c161gmuj15rh15k16lfu8h16b",
"webhook_id": "c015zpn801fzbc701pyx1601z",
"event_id": "c1gdv8xd1g3vn8e1ftw1jf1fj",
"event_type": "contact.created",
"attempt": 1,
"status": "PENDING",
"response_status": 200,
"response_body": "…",
"error_message": null,
"duration_ms": 412,
"is_replay": false,
"replay_of_id": null,
"next_attempt_at": "2026-12-31T00:00:00.000Z",
"created_at": "2026-05-11T09:30:00.000Z",
"updated_at": "2026-06-24T15:45:00.000Z"
}
],
"meta": {
"request_id": "ad589176-ae58-4309-ab58-8e50ac588fe3",
"has_more": false,
"next_cursor": null
}
}/webhooks/{webhookId}/deliveries/{deliveryId}/replayReplay a delivery
Requires webhooks:write · Idempotency-Key required
Queues the original payload for re-delivery as a NEW delivery row, so the log shows both attempts. The X-Mailneo-Event-Id header is unchanged, so a consumer that deduplicates on it — the documented contract — correctly treats the replay as the event it already saw. The timestamp and signature are freshly computed: re-sending the original signature would either fall outside the five-minute tolerance or prove that a captured signature stays valid forever. Idempotency-Key is required, because a retried replay that already ran must not deliver twice.
Path parameters2
webhookIdstringrequired1..64 charsdeliveryIdstringrequired1..64 charsQuery parameters1
dry_runbooleandata: null and a meta.impact block. Requires the resource's :read scope in addition to the scope this endpoint declares. Must be exactly true or false.Header parameters1
Idempotency-Keystringrequired≤ 255 charsIdempotent-Replayed: true. Reusing a key with a different body is a 409 idempotency_key_reused. Keys are remembered for 24 hours.curl -X POST "https://api.mailneo.co/api/v2/webhooks/{webhookId}/deliveries/{deliveryId}/replay" \
-H "x-api-key: $MAILNEO_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"{
"data": "…",
"meta": {
"request_id": "7b3b7463-7a3b-42d0-ad3b-77897c3b75f6",
"dry_run": false
}
}