Platform
Webhooks API
8 endpoints under https://api.mailneo.co/api/v2. Every request carries your key in the X-API-Key header. The scopes used here: webhooks:read, webhooks:write. Each endpoint below states the one it requires. Start with the guide →
listWebhookEndpointsList webhook endpoints
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.
/webhookswebhooks:readExample request
curl "https://api.mailneo.co/api/v2/webhooks?limit=25&order=desc&sort=updated_at" \ -H "X-API-Key: $MAILNEO_API_KEY"
Authorization
- Required scope
webhooks:read. A key without it is refused before the endpoint runs.- Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...
Query parameters7
| Name | Type | Required | Notes |
|---|---|---|---|
| limit | integer | No | default 25 · 1–100 |
| cursor | string | No | max 256 chars |
| order | "asc" | "desc" | No | default "desc" |
| updated_since | string · date-time | No | ISO 8601 |
| sort | "updated_at" | "created_at" | No | default "updated_at" |
| active | "true" | "false" | No | |
| event | string | No | max 64 chars |
Response · 200
Success
data[]Each element of the array.
| Field | Type | Always present | Notes |
|---|---|---|---|
| object | "webhook_endpoint" | Yes | |
| id | string | Yes | |
| name | string | Yes | |
| url | string | Yes | |
| events | string[] | Yes | |
| active | boolean | Yes | |
| description | string · nullable | Yes | |
| signature_version | string | Yes | |
| api_version | string | Yes | |
| header_names | string[] | Yes | |
| consecutive_failures | integer | Yes | |
| last_success_at | string · nullable | Yes | |
| last_failure_at | string · nullable | Yes | |
| disabled_at | string · nullable | Yes | |
| disabled_reason | string · nullable | Yes | |
| created_at | string | Yes | |
| updated_at | string | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| has_more | boolean | Yes | |
| next_cursor | string · nullable | Yes | |
| synced_through | string | No |
createWebhookEndpointRegister a webhook endpoint
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.
/webhookswebhooks:writeExample request
curl -X POST "https://api.mailneo.co/api/v2/webhooks" \
-H "X-API-Key: $MAILNEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "<string>",
"events": [
"<string>"
]
}'Authorization
- Required scope
webhooks:write. A key without it is refused before the endpoint runs.- Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...- Idempotency
Idempotency-Keyis optional.
Query parameters1
| Name | Type | Required | Notes |
|---|---|---|---|
| dry_run | boolean | No | Report what this call WOULD do and commit nothing. Returns data: 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
| Name | Type | Required | Notes |
|---|---|---|---|
| Idempotency-Key | string | No | A caller-chosen unique string, at most 255 characters. Retrying with the same key replays the first response instead of repeating the work; the replay carries Idempotent-Replayed: true. Reusing a key with a different body is a 409 idempotency_key_reused. Keys are remembered for 24 hours. · max 255 chars |
Request body
Body
| Field | Type | Always present | Notes |
|---|---|---|---|
| url | string | Yes | max 2048 chars |
| events | string[] | Yes | |
| name | string | No | max 200 chars |
| description | string · nullable | No | max 1000 chars |
| headers | object | No | open keys |
Response · 200
Success
dataMay be null.
| Field | Type | Always present | Notes |
|---|---|---|---|
| object | "webhook_endpoint" | Yes | |
| id | string | Yes | |
| name | string | Yes | |
| url | string | Yes | |
| events | string[] | Yes | |
| active | boolean | Yes | |
| description | string · nullable | Yes | |
| signature_version | string | Yes | |
| api_version | string | Yes | |
| header_names | string[] | Yes | |
| consecutive_failures | integer | Yes | |
| last_success_at | string · nullable | Yes | |
| last_failure_at | string · nullable | Yes | |
| disabled_at | string · nullable | Yes | |
| disabled_reason | string · nullable | Yes | |
| created_at | string | Yes | |
| updated_at | string | Yes | |
| secret | string | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| dry_run | boolean | Yes | |
| impact | object | No | |
| impact.object | "impact" | Yes | |
| impact.would_create | integer | Yes | |
| impact.would_update | integer | Yes | |
| impact.would_delete | integer | Yes | |
| impact.affected_count | integer | Yes | |
| impact.affected_ids | string[] | Yes | |
| impact.quota | object · nullable | Yes | |
| impact.quota.kind | string | Yes | |
| impact.quota.would_consume | integer | Yes | |
| impact.quota.limit | integer · nullable | Yes | |
| impact.quota.current | integer | Yes |
Response · 201
Created
dataMay be null.
| Field | Type | Always present | Notes |
|---|---|---|---|
| object | "webhook_endpoint" | Yes | |
| id | string | Yes | |
| name | string | Yes | |
| url | string | Yes | |
| events | string[] | Yes | |
| active | boolean | Yes | |
| description | string · nullable | Yes | |
| signature_version | string | Yes | |
| api_version | string | Yes | |
| header_names | string[] | Yes | |
| consecutive_failures | integer | Yes | |
| last_success_at | string · nullable | Yes | |
| last_failure_at | string · nullable | Yes | |
| disabled_at | string · nullable | Yes | |
| disabled_reason | string · nullable | Yes | |
| created_at | string | Yes | |
| updated_at | string | Yes | |
| secret | string | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| dry_run | boolean | Yes | |
| impact | object | No | |
| impact.object | "impact" | Yes | |
| impact.would_create | integer | Yes | |
| impact.would_update | integer | Yes | |
| impact.would_delete | integer | Yes | |
| impact.affected_count | integer | Yes | |
| impact.affected_ids | string[] | Yes | |
| impact.quota | object · nullable | Yes | |
| impact.quota.kind | string | Yes | |
| impact.quota.would_consume | integer | Yes | |
| impact.quota.limit | integer · nullable | Yes | |
| impact.quota.current | integer | Yes |
listWebhookEventTypesList subscribable event types
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.
/webhooks/eventswebhooks:readExample request
curl "https://api.mailneo.co/api/v2/webhooks/events" \ -H "X-API-Key: $MAILNEO_API_KEY"
Authorization
- Required scope
webhooks:read. A key without it is refused before the endpoint runs.- Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...
Response · 200
Success
data[]Each element of the array.
| Field | Type | Always present | Notes |
|---|---|---|---|
| object | "webhook_event_type" | Yes | |
| type | string | Yes | |
| description | string | Yes | |
| object_type | string | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| has_more | boolean | Yes | |
| next_cursor | string · nullable | Yes |
getWebhookEndpointRetrieve a webhook endpoint
The signing secret is not included and cannot be retrieved. If you have lost it, delete this endpoint and register a new one.
/webhooks/{webhookId}webhooks:readExample request
curl "https://api.mailneo.co/api/v2/webhooks/{webhookId}" \
-H "X-API-Key: $MAILNEO_API_KEY"Authorization
- Required scope
webhooks:read. A key without it is refused before the endpoint runs.- Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...
Path parameters1
| Name | Type | Required | Notes |
|---|---|---|---|
| webhookId | string | Yes | max 64 chars |
Response · 200
Success
data
| Field | Type | Always present | Notes |
|---|---|---|---|
| object | "webhook_endpoint" | Yes | |
| id | string | Yes | |
| name | string | Yes | |
| url | string | Yes | |
| events | string[] | Yes | |
| active | boolean | Yes | |
| description | string · nullable | Yes | |
| signature_version | string | Yes | |
| api_version | string | Yes | |
| header_names | string[] | Yes | |
| consecutive_failures | integer | Yes | |
| last_success_at | string · nullable | Yes | |
| last_failure_at | string · nullable | Yes | |
| disabled_at | string · nullable | Yes | |
| disabled_reason | string · nullable | Yes | |
| created_at | string | Yes | |
| updated_at | string | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes |
updateWebhookEndpointUpdate a webhook endpoint
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.
/webhooks/{webhookId}webhooks:writeExample request
curl -X PATCH "https://api.mailneo.co/api/v2/webhooks/{webhookId}" \
-H "X-API-Key: $MAILNEO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "<string>",
"events": [
"<string>"
],
"name": "<string>",
"description": "<string>",
"headers": {},
"active": true
}'Authorization
- Required scope
webhooks:write. A key without it is refused before the endpoint runs.- Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...- Idempotency
Idempotency-Keyis optional.
Path parameters1
| Name | Type | Required | Notes |
|---|---|---|---|
| webhookId | string | Yes | max 64 chars |
Query parameters1
| Name | Type | Required | Notes |
|---|---|---|---|
| dry_run | boolean | No | Report what this call WOULD do and commit nothing. Returns data: 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
| Name | Type | Required | Notes |
|---|---|---|---|
| Idempotency-Key | string | No | A caller-chosen unique string, at most 255 characters. Retrying with the same key replays the first response instead of repeating the work; the replay carries Idempotent-Replayed: true. Reusing a key with a different body is a 409 idempotency_key_reused. Keys are remembered for 24 hours. · max 255 chars |
Request body
Body
| Field | Type | Always present | Notes |
|---|---|---|---|
| url | string | No | max 2048 chars |
| events | string[] | No | |
| name | string | No | max 200 chars |
| description | string · nullable | No | max 1000 chars |
| headers | object | No | open keys |
| active | boolean | No |
Response · 200
Success
data
| Field | Type | Always present | Notes |
|---|---|---|---|
| data | object | object · nullable | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| dry_run | boolean | Yes | |
| impact | object | No | |
| impact.object | "impact" | Yes | |
| impact.would_create | integer | Yes | |
| impact.would_update | integer | Yes | |
| impact.would_delete | integer | Yes | |
| impact.affected_count | integer | Yes | |
| impact.affected_ids | string[] | Yes | |
| impact.quota | object · nullable | Yes | |
| impact.quota.kind | string | Yes | |
| impact.quota.would_consume | integer | Yes | |
| impact.quota.limit | integer · nullable | Yes | |
| impact.quota.current | integer | Yes |
deleteWebhookEndpointDelete a webhook endpoint
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.
/webhooks/{webhookId}webhooks:writeExample request
curl -X DELETE "https://api.mailneo.co/api/v2/webhooks/{webhookId}" \
-H "X-API-Key: $MAILNEO_API_KEY"Authorization
- Required scope
webhooks:write. A key without it is refused before the endpoint runs.- Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...
Path parameters1
| Name | Type | Required | Notes |
|---|---|---|---|
| webhookId | string | Yes | max 64 chars |
Query parameters1
| Name | Type | Required | Notes |
|---|---|---|---|
| dry_run | boolean | No | Report what this call WOULD do and commit nothing. Returns data: 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. |
Response · 200
Success
dataMay be null.
| Field | Type | Always present | Notes |
|---|---|---|---|
| object | "webhook_endpoint" | Yes | |
| id | string | Yes | |
| deleted | true | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| dry_run | boolean | Yes | |
| impact | object | No | |
| impact.object | "impact" | Yes | |
| impact.would_create | integer | Yes | |
| impact.would_update | integer | Yes | |
| impact.would_delete | integer | Yes | |
| impact.affected_count | integer | Yes | |
| impact.affected_ids | string[] | Yes | |
| impact.quota | object · nullable | Yes | |
| impact.quota.kind | string | Yes | |
| impact.quota.would_consume | integer | Yes | |
| impact.quota.limit | integer · nullable | Yes | |
| impact.quota.current | integer | Yes |
listWebhookDeliveriesList an endpoint's deliveries
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.
/webhooks/{webhookId}/deliverieswebhooks:readExample request
curl "https://api.mailneo.co/api/v2/webhooks/{webhookId}/deliveries?limit=25&order=desc" \
-H "X-API-Key: $MAILNEO_API_KEY"Authorization
- Required scope
webhooks:read. A key without it is refused before the endpoint runs.- Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...
Path parameters1
| Name | Type | Required | Notes |
|---|---|---|---|
| webhookId | string | Yes | max 64 chars |
Query parameters4
| Name | Type | Required | Notes |
|---|---|---|---|
| limit | integer | No | default 25 · 1–100 |
| cursor | string | No | max 256 chars |
| order | "asc" | "desc" | No | default "desc" |
| status | "PENDING" | "SUCCEEDED" | "FAILED" | "RETRYING" | "BLOCKED" | No |
Response · 200
Success
data[]Each element of the array.
| Field | Type | Always present | Notes |
|---|---|---|---|
| object | "webhook_delivery" | Yes | |
| id | string | Yes | |
| webhook_id | string | Yes | |
| event_id | string | Yes | |
| event_type | string | Yes | |
| attempt | integer | Yes | |
| status | string | Yes | |
| response_status | integer · nullable | Yes | |
| response_body | string · nullable | Yes | |
| error_message | string · nullable | Yes | |
| duration_ms | integer · nullable | Yes | |
| is_replay | boolean | Yes | |
| replay_of_id | string · nullable | Yes | |
| next_attempt_at | string · nullable | Yes | |
| created_at | string | Yes | |
| updated_at | string | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| has_more | boolean | Yes | |
| next_cursor | string · nullable | Yes |
replayWebhookDeliveryReplay a delivery
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.
/webhooks/{webhookId}/deliveries/{deliveryId}/replaywebhooks:writeExample request
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)"Authorization
- Required scope
webhooks:write. A key without it is refused before the endpoint runs.- Credential
X-API-Key: mk_live_...Authorization: Bearer mk_live_...- Idempotency
Idempotency-Keyis required.
Path parameters2
| Name | Type | Required | Notes |
|---|---|---|---|
| webhookId | string | Yes | max 64 chars |
| deliveryId | string | Yes | max 64 chars |
Query parameters1
| Name | Type | Required | Notes |
|---|---|---|---|
| dry_run | boolean | No | Report what this call WOULD do and commit nothing. Returns data: 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
| Name | Type | Required | Notes |
|---|---|---|---|
| Idempotency-Key | string | Yes | A caller-chosen unique string, at most 255 characters. Retrying with the same key replays the first response instead of repeating the work; the replay carries Idempotent-Replayed: true. Reusing a key with a different body is a 409 idempotency_key_reused. Keys are remembered for 24 hours. · max 255 chars |
Response · 200
Success
data
| Field | Type | Always present | Notes |
|---|---|---|---|
| data | object | object · nullable | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| dry_run | boolean | Yes | |
| impact | object | No | |
| impact.object | "impact" | Yes | |
| impact.would_create | integer | Yes | |
| impact.would_update | integer | Yes | |
| impact.would_delete | integer | Yes | |
| impact.affected_count | integer | Yes | |
| impact.affected_ids | string[] | Yes | |
| impact.quota | object · nullable | Yes | |
| impact.quota.kind | string | Yes | |
| impact.quota.would_consume | integer | Yes | |
| impact.quota.limit | integer · nullable | Yes | |
| impact.quota.current | integer | Yes |
Response · 201
Created
data
| Field | Type | Always present | Notes |
|---|---|---|---|
| data | object | object · nullable | Yes |
meta
| Field | Type | Always present | Notes |
|---|---|---|---|
| request_id | string | Yes | |
| dry_run | boolean | Yes | |
| impact | object | No | |
| impact.object | "impact" | Yes | |
| impact.would_create | integer | Yes | |
| impact.would_update | integer | Yes | |
| impact.would_delete | integer | Yes | |
| impact.affected_count | integer | Yes | |
| impact.affected_ids | string[] | Yes | |
| impact.quota | object · nullable | Yes | |
| impact.quota.kind | string | Yes | |
| impact.quota.would_consume | integer | Yes | |
| impact.quota.limit | integer · nullable | Yes | |
| impact.quota.current | integer | Yes |