Skip to content
machinemail

Webhooks

Webhook endpoints, deliveries and replay.

MethodPathSummary
GET/v0/inboxes/{inbox_id}/webhooksList Webhooks in Inbox
POST/v0/inboxes/{inbox_id}/webhooksCreate Webhook in Inbox
GET/v0/inboxes/{inbox_id}/webhooks/{webhook_id}Get Webhook in Inbox
PATCH/v0/inboxes/{inbox_id}/webhooks/{webhook_id}Update Webhook in Inbox
DELETE/v0/inboxes/{inbox_id}/webhooks/{webhook_id}Delete Webhook in Inbox
GET/v0/inboxes/{inbox_id}/webhooks/{webhook_id}/deliveriesList Deliveries in Inbox
POST/v0/inboxes/{inbox_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/replayReplay Delivery in Inbox
POST/v0/inboxes/{inbox_id}/webhooks/{webhook_id}/testSend Test Event in Inbox
GET/v0/pods/{pod_id}/webhooksList Webhooks in Pod
POST/v0/pods/{pod_id}/webhooksCreate Webhook in Pod
GET/v0/pods/{pod_id}/webhooks/{webhook_id}Get Webhook in Pod
PATCH/v0/pods/{pod_id}/webhooks/{webhook_id}Update Webhook in Pod
DELETE/v0/pods/{pod_id}/webhooks/{webhook_id}Delete Webhook in Pod
GET/v0/pods/{pod_id}/webhooks/{webhook_id}/deliveriesList Deliveries in Pod
POST/v0/pods/{pod_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/replayReplay Delivery in Pod
POST/v0/pods/{pod_id}/webhooks/{webhook_id}/testSend Test Event in Pod
GET/v0/webhooksList Webhooks
POST/v0/webhooksCreate Webhook
GET/v0/webhooks/{webhook_id}Get Webhook
PATCH/v0/webhooks/{webhook_id}Update Webhook
DELETE/v0/webhooks/{webhook_id}Delete Webhook
GET/v0/webhooks/{webhook_id}/deliveriesList Deliveries
POST/v0/webhooks/{webhook_id}/deliveries/{delivery_id}/replayReplay Delivery
POST/v0/webhooks/{webhook_id}/testSend Test Event

List Webhooks in Inbox

http
GET /v0/inboxes/{inbox_id}/webhooks

Same as List Webhooks (GET /v0/webhooks), limited to the inbox.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_read.

Path parameters

inbox_idstringRequired
ID of the inbox.

Query parameters

limitinteger
Maximum number of items to return. Default 50, maximum 500; larger values are capped.
page_tokenstring
next_page_token from the previous page.
ascendingboolean
Sort oldest first.

Response

200 A page of webhooks (application/json)

countintegerRequired
Number of items returned.
limitinteger
Maximum number of items per page. Default 50, maximum 500; larger values are capped.
next_page_tokenstring
Page token for pagination. Absent on the last page.
webhooksWebhook[]Required
Ordered by created_at, newest first unless ascending is set.

Each item of webhooks is a Webhook:

webhook_idstringRequired
ID of the webhook.
urlstringRequired
URL of the webhook endpoint.
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]Required
Event types the webhook receives. Empty: every event type except domain.updated, which is sent only when listed.
pod_idsstring[]
Only events of these pods. Absent: every pod in scope.
inbox_idsstring[]
Only events of these inboxes. Absent: every inbox in scope.
pod_idstring
Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod.
inbox_idstring
Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox.
secretstringRequired
Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read.
enabledbooleanRequired
Whether new events are delivered. Webhooks are never disabled automatically.
updated_atstring (date-time)Required
Time at which the webhook was last updated.
created_atstring (date-time)Required
Time at which the webhook was created.
client_idstring
Client ID given at creation (idempotency key).
status"active" | "disabled"Required
active or disabled; mirrors enabled.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/webhooks" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "webhooks": [
    {
      "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
      "url": "https://example.com/webhooks/machinemail",
      "event_types": ["message.received"],
      "pod_ids": ["pod_01k6x2m9qd0001b8j4mr0w3d5f"],
      "inbox_ids": ["agent@machinemail.dev"],
      "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
      "inbox_id": "agent@machinemail.dev",
      "secret": "whsec_0123456789abcdef",
      "enabled": true,
      "updated_at": "2026-10-01T12:00:00.000Z",
      "created_at": "2026-10-01T12:00:00.000Z",
      "client_id": "my-client-id",
      "status": "active"
    }
  ]
}

Create Webhook in Inbox

http
POST /v0/inboxes/{inbox_id}/webhooks

Same as Create Webhook (POST /v0/webhooks), limited to the inbox.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_create.

Path parameters

inbox_idstringRequired
ID of the inbox.

Request body

Content type: application/json.

urlstringRequired
Endpoint URL: a public http(s) URL (private and loopback addresses are rejected, and re-checked before every delivery).
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]
Event types the webhook receives. Omit it or send an empty array to receive every event type except domain.updated, which is sent only when listed.
client_idstring
Idempotency key: a create with a client_id already used in the organization returns that webhook with 200.
inbox_idsstring[]
Only deliver events of these inboxes.
pod_idsstring[]
Only deliver events of these pods.

Response

200 A webhook with this client_id already exists; it is returned unchanged. (application/json)

webhook_idstringRequired
ID of the webhook.
urlstringRequired
URL of the webhook endpoint.
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]Required
Event types the webhook receives. Empty: every event type except domain.updated, which is sent only when listed.
pod_idsstring[]
Only events of these pods. Absent: every pod in scope.
inbox_idsstring[]
Only events of these inboxes. Absent: every inbox in scope.
pod_idstring
Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod.
inbox_idstring
Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox.
secretstringRequired
Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read.
enabledbooleanRequired
Whether new events are delivered. Webhooks are never disabled automatically.
updated_atstring (date-time)Required
Time at which the webhook was last updated.
created_atstring (date-time)Required
Time at which the webhook was created.
client_idstring
Client ID given at creation (idempotency key).
status"active" | "disabled"Required
active or disabled; mirrors enabled.

201 Created (application/json)

webhook_idstringRequired
ID of the webhook.
urlstringRequired
URL of the webhook endpoint.
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]Required
Event types the webhook receives. Empty: every event type except domain.updated, which is sent only when listed.
pod_idsstring[]
Only events of these pods. Absent: every pod in scope.
inbox_idsstring[]
Only events of these inboxes. Absent: every inbox in scope.
pod_idstring
Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod.
inbox_idstring
Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox.
secretstringRequired
Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read.
enabledbooleanRequired
Whether new events are delivered. Webhooks are never disabled automatically.
updated_atstring (date-time)Required
Time at which the webhook was last updated.
created_atstring (date-time)Required
Time at which the webhook was created.
client_idstring
Client ID given at creation (idempotency key).
status"active" | "disabled"Required
active or disabled; mirrors enabled.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X POST "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/webhooks" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/webhooks/machinemail",
  "event_types": [
    "message.received"
  ]
}'
Response 200
{
  "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
  "url": "https://example.com/webhooks/machinemail",
  "event_types": ["message.received"],
  "pod_ids": ["pod_01k6x2m9qd0001b8j4mr0w3d5f"],
  "inbox_ids": ["agent@machinemail.dev"],
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "secret": "whsec_0123456789abcdef",
  "enabled": true,
  "updated_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "status": "active"
}

Get Webhook in Inbox

http
GET /v0/inboxes/{inbox_id}/webhooks/{webhook_id}

Same as Get Webhook (GET /v0/webhooks/{webhook_id}), limited to the inbox.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_read.

Path parameters

inbox_idstringRequired
ID of the inbox.
webhook_idstringRequired
ID of the webhook.

Response

200 The webhook (application/json)

webhook_idstringRequired
ID of the webhook.
urlstringRequired
URL of the webhook endpoint.
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]Required
Event types the webhook receives. Empty: every event type except domain.updated, which is sent only when listed.
pod_idsstring[]
Only events of these pods. Absent: every pod in scope.
inbox_idsstring[]
Only events of these inboxes. Absent: every inbox in scope.
pod_idstring
Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod.
inbox_idstring
Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox.
secretstringRequired
Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read.
enabledbooleanRequired
Whether new events are delivered. Webhooks are never disabled automatically.
updated_atstring (date-time)Required
Time at which the webhook was last updated.
created_atstring (date-time)Required
Time at which the webhook was created.
client_idstring
Client ID given at creation (idempotency key).
status"active" | "disabled"Required
active or disabled; mirrors enabled.

Errors

StatusMeaning
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
  "url": "https://example.com/webhooks/machinemail",
  "event_types": ["message.received"],
  "pod_ids": ["pod_01k6x2m9qd0001b8j4mr0w3d5f"],
  "inbox_ids": ["agent@machinemail.dev"],
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "secret": "whsec_0123456789abcdef",
  "enabled": true,
  "updated_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "status": "active"
}

Update Webhook in Inbox

http
PATCH /v0/inboxes/{inbox_id}/webhooks/{webhook_id}

Same as Update Webhook (PATCH /v0/webhooks/{webhook_id}), limited to the inbox.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_update.

Path parameters

inbox_idstringRequired
ID of the inbox.
webhook_idstringRequired
ID of the webhook.

Request body

Content type: application/json.

event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]
Replaces the subscribed event types in full (not a merge). An empty array subscribes to every event type except domain.updated, which is sent only when listed. Omit it to keep the types.
enabledboolean
false disables the webhook: no new deliveries are queued (deliveries already queued are still attempted). true enables it again. Events that occurred while it was disabled are not delivered later.
add_inbox_idsstring[]
Inboxes to add to inbox_ids.
remove_inbox_idsstring[]
Inboxes to remove from inbox_ids.
add_pod_idsstring[]
Pods to add to pod_ids.
remove_pod_idsstring[]
Pods to remove from pod_ids.
urlstring
New endpoint URL: a public http(s) URL.

Response

200 The updated webhook (application/json)

webhook_idstringRequired
ID of the webhook.
urlstringRequired
URL of the webhook endpoint.
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]Required
Event types the webhook receives. Empty: every event type except domain.updated, which is sent only when listed.
pod_idsstring[]
Only events of these pods. Absent: every pod in scope.
inbox_idsstring[]
Only events of these inboxes. Absent: every inbox in scope.
pod_idstring
Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod.
inbox_idstring
Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox.
secretstringRequired
Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read.
enabledbooleanRequired
Whether new events are delivered. Webhooks are never disabled automatically.
updated_atstring (date-time)Required
Time at which the webhook was last updated.
created_atstring (date-time)Required
Time at which the webhook was created.
client_idstring
Client ID given at creation (idempotency key).
status"active" | "disabled"Required
active or disabled; mirrors enabled.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X PATCH "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_types": [
    "message.received"
  ],
  "url": "https://example.com/webhooks/machinemail"
}'
Response 200
{
  "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
  "url": "https://example.com/webhooks/machinemail",
  "event_types": ["message.received"],
  "pod_ids": ["pod_01k6x2m9qd0001b8j4mr0w3d5f"],
  "inbox_ids": ["agent@machinemail.dev"],
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "secret": "whsec_0123456789abcdef",
  "enabled": true,
  "updated_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "status": "active"
}

Delete Webhook in Inbox

http
DELETE /v0/inboxes/{inbox_id}/webhooks/{webhook_id}

Same as Delete Webhook (DELETE /v0/webhooks/{webhook_id}), limited to the inbox.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_delete.

Path parameters

inbox_idstringRequired
ID of the inbox.
webhook_idstringRequired
ID of the webhook.

Response

200 Deleted (application/json)

oktrueRequired

Errors

StatusMeaning
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X DELETE "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "ok": true
}

List Deliveries in Inbox

http
GET /v0/inboxes/{inbox_id}/webhooks/{webhook_id}/deliveries

Same as List Deliveries (GET /v0/webhooks/{webhook_id}/deliveries), limited to the inbox.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_read.

Path parameters

inbox_idstringRequired
ID of the inbox.
webhook_idstringRequired
ID of the webhook.

Query parameters

limitinteger
Maximum number of items to return. Default 50, maximum 500; larger values are capped.
page_tokenstring
next_page_token from the previous page.
ascendingboolean
Sort oldest first.
status"pending" | "delivered" | "failed"
Only deliveries with this status.

Response

200 A page of deliveries (application/json)

countintegerRequired
Number of items returned.
limitinteger
Maximum number of items per page. Default 50, maximum 500; larger values are capped.
next_page_tokenstring
Page token for pagination. Absent on the last page.
deliveriesWebhookDelivery[]Required
Ordered by created_at, newest first unless ascending is set.

Each item of deliveries is a WebhookDelivery:

delivery_idstringRequired
ID of the delivery (X-MachineMail-Delivery-Id).
webhook_idstringRequired
ID of the webhook.
event_idstringRequired
ID of the event (X-MachineMail-Event-Id).
event_type"message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test"Required
Type of event. AgentMail has no message.received.quarantined, domain.updated, inbox.created, inbox.expired, draft.sent or webhook.test; MachineMail does not emit message.opened or calendar.event.*. domain.updated is sent whenever a DNS check changes the domain status or a record status (the change to VERIFIED also sends domain.verified); webhooks and WebSocket subscriptions receive it only when their event_types list it, while the SSE stream and the event log always include it.
attemptintegerRequired
Number of attempts so far.
status"pending" | "delivered" | "failed"Required
pending until a 2xx response (delivered), or failed when retries stop (5 days after the event) or the webhook or event was deleted.
response_statusinteger
HTTP status of the last attempt.
response_bodystring
First 1024 bytes of the last response body.
next_attempt_atstring (date-time)
When the next attempt is due, while pending.
last_errorstring
Error of the last failed attempt (HTTP 500, a network error).
delivered_atstring (date-time)
When the delivery succeeded.
created_atstring (date-time)Required
When the delivery was queued.
updated_atstring (date-time)Required
Last change to the delivery.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k/deliveries" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "deliveries": [
    {
      "delivery_id": "dlv_01k6x3f1ns0000j5s1vz7d0m2p",
      "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
      "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
      "event_type": "message.received",
      "attempt": 1,
      "status": "pending",
      "response_status": 1,
      "response_body": "string",
      "next_attempt_at": "2026-10-01T12:00:00.000Z",
      "last_error": "string",
      "delivered_at": "2026-10-01T12:00:00.000Z",
      "created_at": "2026-10-01T12:00:00.000Z",
      "updated_at": "2026-10-01T12:00:00.000Z"
    }
  ]
}

Replay Delivery in Inbox

http
POST /v0/inboxes/{inbox_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/replay

Same as Replay Delivery (POST /v0/webhooks/{webhook_id}/deliveries/{delivery_id}/replay), limited to the inbox.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_update.

Path parameters

inbox_idstringRequired
ID of the inbox.
webhook_idstringRequired
ID of the webhook.
delivery_idstringRequired
ID of the delivery to replay.

Response

200 The new delivery (application/json)

delivery_idstringRequired
ID of the delivery (X-MachineMail-Delivery-Id).
webhook_idstringRequired
ID of the webhook.
event_idstringRequired
ID of the event (X-MachineMail-Event-Id).
event_type"message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test"Required
Type of event. AgentMail has no message.received.quarantined, domain.updated, inbox.created, inbox.expired, draft.sent or webhook.test; MachineMail does not emit message.opened or calendar.event.*. domain.updated is sent whenever a DNS check changes the domain status or a record status (the change to VERIFIED also sends domain.verified); webhooks and WebSocket subscriptions receive it only when their event_types list it, while the SSE stream and the event log always include it.
attemptintegerRequired
Number of attempts so far.
status"pending" | "delivered" | "failed"Required
pending until a 2xx response (delivered), or failed when retries stop (5 days after the event) or the webhook or event was deleted.
response_statusinteger
HTTP status of the last attempt.
response_bodystring
First 1024 bytes of the last response body.
next_attempt_atstring (date-time)
When the next attempt is due, while pending.
last_errorstring
Error of the last failed attempt (HTTP 500, a network error).
delivered_atstring (date-time)
When the delivery succeeded.
created_atstring (date-time)Required
When the delivery was queued.
updated_atstring (date-time)Required
Last change to the delivery.

Errors

StatusMeaning
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X POST "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k/deliveries/dlv_01k6x3f1ns0000j5s1vz7d0m2p/replay" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "delivery_id": "dlv_01k6x3f1ns0000j5s1vz7d0m2p",
  "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
  "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
  "event_type": "message.received",
  "attempt": 1,
  "status": "pending",
  "response_status": 1,
  "response_body": "string",
  "next_attempt_at": "2026-10-01T12:00:00.000Z",
  "last_error": "string",
  "delivered_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z"
}

Send Test Event in Inbox

http
POST /v0/inboxes/{inbox_id}/webhooks/{webhook_id}/test

Same as Send Test Event (POST /v0/webhooks/{webhook_id}/test), limited to the inbox.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_update.

Path parameters

inbox_idstringRequired
ID of the inbox.
webhook_idstringRequired
ID of the webhook.

Response

200 The test delivery (application/json)

delivery_idstringRequired
ID of the delivery (X-MachineMail-Delivery-Id).
webhook_idstringRequired
ID of the webhook.
event_idstringRequired
ID of the event (X-MachineMail-Event-Id).
event_type"message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test"Required
Type of event. AgentMail has no message.received.quarantined, domain.updated, inbox.created, inbox.expired, draft.sent or webhook.test; MachineMail does not emit message.opened or calendar.event.*. domain.updated is sent whenever a DNS check changes the domain status or a record status (the change to VERIFIED also sends domain.verified); webhooks and WebSocket subscriptions receive it only when their event_types list it, while the SSE stream and the event log always include it.
attemptintegerRequired
Number of attempts so far.
status"pending" | "delivered" | "failed"Required
pending until a 2xx response (delivered), or failed when retries stop (5 days after the event) or the webhook or event was deleted.
response_statusinteger
HTTP status of the last attempt.
response_bodystring
First 1024 bytes of the last response body.
next_attempt_atstring (date-time)
When the next attempt is due, while pending.
last_errorstring
Error of the last failed attempt (HTTP 500, a network error).
delivered_atstring (date-time)
When the delivery succeeded.
created_atstring (date-time)Required
When the delivery was queued.
updated_atstring (date-time)Required
Last change to the delivery.

Errors

StatusMeaning
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X POST "https://api.machinemail.dev/v0/inboxes/agent@machinemail.dev/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k/test" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "delivery_id": "dlv_01k6x3f1ns0000j5s1vz7d0m2p",
  "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
  "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
  "event_type": "message.received",
  "attempt": 1,
  "status": "pending",
  "response_status": 1,
  "response_body": "string",
  "next_attempt_at": "2026-10-01T12:00:00.000Z",
  "last_error": "string",
  "delivered_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z"
}

List Webhooks in Pod

http
GET /v0/pods/{pod_id}/webhooks

Same as List Webhooks (GET /v0/webhooks), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_read.

Path parameters

pod_idstringRequired
ID of the pod.

Query parameters

limitinteger
Maximum number of items to return. Default 50, maximum 500; larger values are capped.
page_tokenstring
next_page_token from the previous page.
ascendingboolean
Sort oldest first.

Response

200 A page of webhooks (application/json)

countintegerRequired
Number of items returned.
limitinteger
Maximum number of items per page. Default 50, maximum 500; larger values are capped.
next_page_tokenstring
Page token for pagination. Absent on the last page.
webhooksWebhook[]Required
Ordered by created_at, newest first unless ascending is set.

Each item of webhooks is a Webhook:

webhook_idstringRequired
ID of the webhook.
urlstringRequired
URL of the webhook endpoint.
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]Required
Event types the webhook receives. Empty: every event type except domain.updated, which is sent only when listed.
pod_idsstring[]
Only events of these pods. Absent: every pod in scope.
inbox_idsstring[]
Only events of these inboxes. Absent: every inbox in scope.
pod_idstring
Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod.
inbox_idstring
Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox.
secretstringRequired
Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read.
enabledbooleanRequired
Whether new events are delivered. Webhooks are never disabled automatically.
updated_atstring (date-time)Required
Time at which the webhook was last updated.
created_atstring (date-time)Required
Time at which the webhook was created.
client_idstring
Client ID given at creation (idempotency key).
status"active" | "disabled"Required
active or disabled; mirrors enabled.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/webhooks" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "webhooks": [
    {
      "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
      "url": "https://example.com/webhooks/machinemail",
      "event_types": ["message.received"],
      "pod_ids": ["pod_01k6x2m9qd0001b8j4mr0w3d5f"],
      "inbox_ids": ["agent@machinemail.dev"],
      "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
      "inbox_id": "agent@machinemail.dev",
      "secret": "whsec_0123456789abcdef",
      "enabled": true,
      "updated_at": "2026-10-01T12:00:00.000Z",
      "created_at": "2026-10-01T12:00:00.000Z",
      "client_id": "my-client-id",
      "status": "active"
    }
  ]
}

Create Webhook in Pod

http
POST /v0/pods/{pod_id}/webhooks

Same as Create Webhook (POST /v0/webhooks), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_create.

Path parameters

pod_idstringRequired
ID of the pod.

Request body

Content type: application/json.

urlstringRequired
Endpoint URL: a public http(s) URL (private and loopback addresses are rejected, and re-checked before every delivery).
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]
Event types the webhook receives. Omit it or send an empty array to receive every event type except domain.updated, which is sent only when listed.
client_idstring
Idempotency key: a create with a client_id already used in the organization returns that webhook with 200.
inbox_idsstring[]
Only deliver events of these inboxes.
pod_idsstring[]
Only deliver events of these pods.

Response

200 A webhook with this client_id already exists; it is returned unchanged. (application/json)

webhook_idstringRequired
ID of the webhook.
urlstringRequired
URL of the webhook endpoint.
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]Required
Event types the webhook receives. Empty: every event type except domain.updated, which is sent only when listed.
pod_idsstring[]
Only events of these pods. Absent: every pod in scope.
inbox_idsstring[]
Only events of these inboxes. Absent: every inbox in scope.
pod_idstring
Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod.
inbox_idstring
Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox.
secretstringRequired
Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read.
enabledbooleanRequired
Whether new events are delivered. Webhooks are never disabled automatically.
updated_atstring (date-time)Required
Time at which the webhook was last updated.
created_atstring (date-time)Required
Time at which the webhook was created.
client_idstring
Client ID given at creation (idempotency key).
status"active" | "disabled"Required
active or disabled; mirrors enabled.

201 Created (application/json)

webhook_idstringRequired
ID of the webhook.
urlstringRequired
URL of the webhook endpoint.
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]Required
Event types the webhook receives. Empty: every event type except domain.updated, which is sent only when listed.
pod_idsstring[]
Only events of these pods. Absent: every pod in scope.
inbox_idsstring[]
Only events of these inboxes. Absent: every inbox in scope.
pod_idstring
Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod.
inbox_idstring
Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox.
secretstringRequired
Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read.
enabledbooleanRequired
Whether new events are delivered. Webhooks are never disabled automatically.
updated_atstring (date-time)Required
Time at which the webhook was last updated.
created_atstring (date-time)Required
Time at which the webhook was created.
client_idstring
Client ID given at creation (idempotency key).
status"active" | "disabled"Required
active or disabled; mirrors enabled.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X POST "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/webhooks" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/webhooks/machinemail",
  "event_types": [
    "message.received"
  ]
}'
Response 200
{
  "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
  "url": "https://example.com/webhooks/machinemail",
  "event_types": ["message.received"],
  "pod_ids": ["pod_01k6x2m9qd0001b8j4mr0w3d5f"],
  "inbox_ids": ["agent@machinemail.dev"],
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "secret": "whsec_0123456789abcdef",
  "enabled": true,
  "updated_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "status": "active"
}

Get Webhook in Pod

http
GET /v0/pods/{pod_id}/webhooks/{webhook_id}

Same as Get Webhook (GET /v0/webhooks/{webhook_id}), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_read.

Path parameters

pod_idstringRequired
ID of the pod.
webhook_idstringRequired
ID of the webhook.

Response

200 The webhook (application/json)

webhook_idstringRequired
ID of the webhook.
urlstringRequired
URL of the webhook endpoint.
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]Required
Event types the webhook receives. Empty: every event type except domain.updated, which is sent only when listed.
pod_idsstring[]
Only events of these pods. Absent: every pod in scope.
inbox_idsstring[]
Only events of these inboxes. Absent: every inbox in scope.
pod_idstring
Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod.
inbox_idstring
Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox.
secretstringRequired
Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read.
enabledbooleanRequired
Whether new events are delivered. Webhooks are never disabled automatically.
updated_atstring (date-time)Required
Time at which the webhook was last updated.
created_atstring (date-time)Required
Time at which the webhook was created.
client_idstring
Client ID given at creation (idempotency key).
status"active" | "disabled"Required
active or disabled; mirrors enabled.

Errors

StatusMeaning
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
  "url": "https://example.com/webhooks/machinemail",
  "event_types": ["message.received"],
  "pod_ids": ["pod_01k6x2m9qd0001b8j4mr0w3d5f"],
  "inbox_ids": ["agent@machinemail.dev"],
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "secret": "whsec_0123456789abcdef",
  "enabled": true,
  "updated_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "status": "active"
}

Update Webhook in Pod

http
PATCH /v0/pods/{pod_id}/webhooks/{webhook_id}

Same as Update Webhook (PATCH /v0/webhooks/{webhook_id}), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_update.

Path parameters

pod_idstringRequired
ID of the pod.
webhook_idstringRequired
ID of the webhook.

Request body

Content type: application/json.

event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]
Replaces the subscribed event types in full (not a merge). An empty array subscribes to every event type except domain.updated, which is sent only when listed. Omit it to keep the types.
enabledboolean
false disables the webhook: no new deliveries are queued (deliveries already queued are still attempted). true enables it again. Events that occurred while it was disabled are not delivered later.
add_inbox_idsstring[]
Inboxes to add to inbox_ids.
remove_inbox_idsstring[]
Inboxes to remove from inbox_ids.
add_pod_idsstring[]
Pods to add to pod_ids.
remove_pod_idsstring[]
Pods to remove from pod_ids.
urlstring
New endpoint URL: a public http(s) URL.

Response

200 The updated webhook (application/json)

webhook_idstringRequired
ID of the webhook.
urlstringRequired
URL of the webhook endpoint.
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]Required
Event types the webhook receives. Empty: every event type except domain.updated, which is sent only when listed.
pod_idsstring[]
Only events of these pods. Absent: every pod in scope.
inbox_idsstring[]
Only events of these inboxes. Absent: every inbox in scope.
pod_idstring
Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod.
inbox_idstring
Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox.
secretstringRequired
Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read.
enabledbooleanRequired
Whether new events are delivered. Webhooks are never disabled automatically.
updated_atstring (date-time)Required
Time at which the webhook was last updated.
created_atstring (date-time)Required
Time at which the webhook was created.
client_idstring
Client ID given at creation (idempotency key).
status"active" | "disabled"Required
active or disabled; mirrors enabled.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X PATCH "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_types": [
    "message.received"
  ],
  "url": "https://example.com/webhooks/machinemail"
}'
Response 200
{
  "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
  "url": "https://example.com/webhooks/machinemail",
  "event_types": ["message.received"],
  "pod_ids": ["pod_01k6x2m9qd0001b8j4mr0w3d5f"],
  "inbox_ids": ["agent@machinemail.dev"],
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "secret": "whsec_0123456789abcdef",
  "enabled": true,
  "updated_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "status": "active"
}

Delete Webhook in Pod

http
DELETE /v0/pods/{pod_id}/webhooks/{webhook_id}

Same as Delete Webhook (DELETE /v0/webhooks/{webhook_id}), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_delete.

Path parameters

pod_idstringRequired
ID of the pod.
webhook_idstringRequired
ID of the webhook.

Response

200 Deleted (application/json)

oktrueRequired

Errors

StatusMeaning
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X DELETE "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "ok": true
}

List Deliveries in Pod

http
GET /v0/pods/{pod_id}/webhooks/{webhook_id}/deliveries

Same as List Deliveries (GET /v0/webhooks/{webhook_id}/deliveries), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_read.

Path parameters

pod_idstringRequired
ID of the pod.
webhook_idstringRequired
ID of the webhook.

Query parameters

limitinteger
Maximum number of items to return. Default 50, maximum 500; larger values are capped.
page_tokenstring
next_page_token from the previous page.
ascendingboolean
Sort oldest first.
status"pending" | "delivered" | "failed"
Only deliveries with this status.

Response

200 A page of deliveries (application/json)

countintegerRequired
Number of items returned.
limitinteger
Maximum number of items per page. Default 50, maximum 500; larger values are capped.
next_page_tokenstring
Page token for pagination. Absent on the last page.
deliveriesWebhookDelivery[]Required
Ordered by created_at, newest first unless ascending is set.

Each item of deliveries is a WebhookDelivery:

delivery_idstringRequired
ID of the delivery (X-MachineMail-Delivery-Id).
webhook_idstringRequired
ID of the webhook.
event_idstringRequired
ID of the event (X-MachineMail-Event-Id).
event_type"message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test"Required
Type of event. AgentMail has no message.received.quarantined, domain.updated, inbox.created, inbox.expired, draft.sent or webhook.test; MachineMail does not emit message.opened or calendar.event.*. domain.updated is sent whenever a DNS check changes the domain status or a record status (the change to VERIFIED also sends domain.verified); webhooks and WebSocket subscriptions receive it only when their event_types list it, while the SSE stream and the event log always include it.
attemptintegerRequired
Number of attempts so far.
status"pending" | "delivered" | "failed"Required
pending until a 2xx response (delivered), or failed when retries stop (5 days after the event) or the webhook or event was deleted.
response_statusinteger
HTTP status of the last attempt.
response_bodystring
First 1024 bytes of the last response body.
next_attempt_atstring (date-time)
When the next attempt is due, while pending.
last_errorstring
Error of the last failed attempt (HTTP 500, a network error).
delivered_atstring (date-time)
When the delivery succeeded.
created_atstring (date-time)Required
When the delivery was queued.
updated_atstring (date-time)Required
Last change to the delivery.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k/deliveries" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "deliveries": [
    {
      "delivery_id": "dlv_01k6x3f1ns0000j5s1vz7d0m2p",
      "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
      "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
      "event_type": "message.received",
      "attempt": 1,
      "status": "pending",
      "response_status": 1,
      "response_body": "string",
      "next_attempt_at": "2026-10-01T12:00:00.000Z",
      "last_error": "string",
      "delivered_at": "2026-10-01T12:00:00.000Z",
      "created_at": "2026-10-01T12:00:00.000Z",
      "updated_at": "2026-10-01T12:00:00.000Z"
    }
  ]
}

Replay Delivery in Pod

http
POST /v0/pods/{pod_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/replay

Same as Replay Delivery (POST /v0/webhooks/{webhook_id}/deliveries/{delivery_id}/replay), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_update.

Path parameters

pod_idstringRequired
ID of the pod.
webhook_idstringRequired
ID of the webhook.
delivery_idstringRequired
ID of the delivery to replay.

Response

200 The new delivery (application/json)

delivery_idstringRequired
ID of the delivery (X-MachineMail-Delivery-Id).
webhook_idstringRequired
ID of the webhook.
event_idstringRequired
ID of the event (X-MachineMail-Event-Id).
event_type"message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test"Required
Type of event. AgentMail has no message.received.quarantined, domain.updated, inbox.created, inbox.expired, draft.sent or webhook.test; MachineMail does not emit message.opened or calendar.event.*. domain.updated is sent whenever a DNS check changes the domain status or a record status (the change to VERIFIED also sends domain.verified); webhooks and WebSocket subscriptions receive it only when their event_types list it, while the SSE stream and the event log always include it.
attemptintegerRequired
Number of attempts so far.
status"pending" | "delivered" | "failed"Required
pending until a 2xx response (delivered), or failed when retries stop (5 days after the event) or the webhook or event was deleted.
response_statusinteger
HTTP status of the last attempt.
response_bodystring
First 1024 bytes of the last response body.
next_attempt_atstring (date-time)
When the next attempt is due, while pending.
last_errorstring
Error of the last failed attempt (HTTP 500, a network error).
delivered_atstring (date-time)
When the delivery succeeded.
created_atstring (date-time)Required
When the delivery was queued.
updated_atstring (date-time)Required
Last change to the delivery.

Errors

StatusMeaning
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X POST "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k/deliveries/dlv_01k6x3f1ns0000j5s1vz7d0m2p/replay" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "delivery_id": "dlv_01k6x3f1ns0000j5s1vz7d0m2p",
  "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
  "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
  "event_type": "message.received",
  "attempt": 1,
  "status": "pending",
  "response_status": 1,
  "response_body": "string",
  "next_attempt_at": "2026-10-01T12:00:00.000Z",
  "last_error": "string",
  "delivered_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z"
}

Send Test Event in Pod

http
POST /v0/pods/{pod_id}/webhooks/{webhook_id}/test

Same as Send Test Event (POST /v0/webhooks/{webhook_id}/test), limited to the pod.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_update.

Path parameters

pod_idstringRequired
ID of the pod.
webhook_idstringRequired
ID of the webhook.

Response

200 The test delivery (application/json)

delivery_idstringRequired
ID of the delivery (X-MachineMail-Delivery-Id).
webhook_idstringRequired
ID of the webhook.
event_idstringRequired
ID of the event (X-MachineMail-Event-Id).
event_type"message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test"Required
Type of event. AgentMail has no message.received.quarantined, domain.updated, inbox.created, inbox.expired, draft.sent or webhook.test; MachineMail does not emit message.opened or calendar.event.*. domain.updated is sent whenever a DNS check changes the domain status or a record status (the change to VERIFIED also sends domain.verified); webhooks and WebSocket subscriptions receive it only when their event_types list it, while the SSE stream and the event log always include it.
attemptintegerRequired
Number of attempts so far.
status"pending" | "delivered" | "failed"Required
pending until a 2xx response (delivered), or failed when retries stop (5 days after the event) or the webhook or event was deleted.
response_statusinteger
HTTP status of the last attempt.
response_bodystring
First 1024 bytes of the last response body.
next_attempt_atstring (date-time)
When the next attempt is due, while pending.
last_errorstring
Error of the last failed attempt (HTTP 500, a network error).
delivered_atstring (date-time)
When the delivery succeeded.
created_atstring (date-time)Required
When the delivery was queued.
updated_atstring (date-time)Required
Last change to the delivery.

Errors

StatusMeaning
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X POST "https://api.machinemail.dev/v0/pods/pod_01k6x2m9qd0001b8j4mr0w3d5f/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k/test" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "delivery_id": "dlv_01k6x3f1ns0000j5s1vz7d0m2p",
  "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
  "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
  "event_type": "message.received",
  "attempt": 1,
  "status": "pending",
  "response_status": 1,
  "response_body": "string",
  "next_attempt_at": "2026-10-01T12:00:00.000Z",
  "last_error": "string",
  "delivered_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z"
}

List Webhooks

http
GET /v0/webhooks

With an organization credential, every webhook of the organization, paginated. With a pod or inbox credential (or under a scoped path), the webhooks in that scope from the newest 500, in one page without next_page_token.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_read.

Query parameters

limitinteger
Maximum number of items to return. Default 50, maximum 500; larger values are capped.
page_tokenstring
next_page_token from the previous page.
ascendingboolean
Sort oldest first.

Response

200 A page of webhooks (application/json)

countintegerRequired
Number of items returned.
limitinteger
Maximum number of items per page. Default 50, maximum 500; larger values are capped.
next_page_tokenstring
Page token for pagination. Absent on the last page.
webhooksWebhook[]Required
Ordered by created_at, newest first unless ascending is set.

Each item of webhooks is a Webhook:

webhook_idstringRequired
ID of the webhook.
urlstringRequired
URL of the webhook endpoint.
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]Required
Event types the webhook receives. Empty: every event type except domain.updated, which is sent only when listed.
pod_idsstring[]
Only events of these pods. Absent: every pod in scope.
inbox_idsstring[]
Only events of these inboxes. Absent: every inbox in scope.
pod_idstring
Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod.
inbox_idstring
Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox.
secretstringRequired
Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read.
enabledbooleanRequired
Whether new events are delivered. Webhooks are never disabled automatically.
updated_atstring (date-time)Required
Time at which the webhook was last updated.
created_atstring (date-time)Required
Time at which the webhook was created.
client_idstring
Client ID given at creation (idempotency key).
status"active" | "disabled"Required
active or disabled; mirrors enabled.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl "https://api.machinemail.dev/v0/webhooks" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "webhooks": [
    {
      "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
      "url": "https://example.com/webhooks/machinemail",
      "event_types": ["message.received"],
      "pod_ids": ["pod_01k6x2m9qd0001b8j4mr0w3d5f"],
      "inbox_ids": ["agent@machinemail.dev"],
      "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
      "inbox_id": "agent@machinemail.dev",
      "secret": "whsec_0123456789abcdef",
      "enabled": true,
      "updated_at": "2026-10-01T12:00:00.000Z",
      "created_at": "2026-10-01T12:00:00.000Z",
      "client_id": "my-client-id",
      "status": "active"
    }
  ]
}

Create Webhook

http
POST /v0/webhooks

The response includes secret (whsec_...), the key of the X-MachineMail-Signature HMAC. See the webhooks section of this document for the request MachineMail sends for each event type.

Under /v0/pods/{pod_id} or /v0/inboxes/{inbox_id}, or with a pod- or inbox-scoped credential, the webhook belongs to that pod or inbox (pod_id, inbox_id) and never receives events outside it.

A webhook delivers no event its creator could not read: each listed event type needs its read permission (message.* needs message_read, inbox.* inbox_read, domain.* domain_read, draft.* draft_read, webhook.test webhook_read), else 403. An empty event_types from a credential without all of them is stored as the types it may read (403 when there are none).

Custom delivery headers are not supported.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_create.

Request body

Content type: application/json.

urlstringRequired
Endpoint URL: a public http(s) URL (private and loopback addresses are rejected, and re-checked before every delivery).
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]
Event types the webhook receives. Omit it or send an empty array to receive every event type except domain.updated, which is sent only when listed.
client_idstring
Idempotency key: a create with a client_id already used in the organization returns that webhook with 200.
inbox_idsstring[]
Only deliver events of these inboxes.
pod_idsstring[]
Only deliver events of these pods.

Response

200 A webhook with this client_id already exists; it is returned unchanged. (application/json)

webhook_idstringRequired
ID of the webhook.
urlstringRequired
URL of the webhook endpoint.
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]Required
Event types the webhook receives. Empty: every event type except domain.updated, which is sent only when listed.
pod_idsstring[]
Only events of these pods. Absent: every pod in scope.
inbox_idsstring[]
Only events of these inboxes. Absent: every inbox in scope.
pod_idstring
Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod.
inbox_idstring
Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox.
secretstringRequired
Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read.
enabledbooleanRequired
Whether new events are delivered. Webhooks are never disabled automatically.
updated_atstring (date-time)Required
Time at which the webhook was last updated.
created_atstring (date-time)Required
Time at which the webhook was created.
client_idstring
Client ID given at creation (idempotency key).
status"active" | "disabled"Required
active or disabled; mirrors enabled.

201 Created (application/json)

webhook_idstringRequired
ID of the webhook.
urlstringRequired
URL of the webhook endpoint.
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]Required
Event types the webhook receives. Empty: every event type except domain.updated, which is sent only when listed.
pod_idsstring[]
Only events of these pods. Absent: every pod in scope.
inbox_idsstring[]
Only events of these inboxes. Absent: every inbox in scope.
pod_idstring
Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod.
inbox_idstring
Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox.
secretstringRequired
Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read.
enabledbooleanRequired
Whether new events are delivered. Webhooks are never disabled automatically.
updated_atstring (date-time)Required
Time at which the webhook was last updated.
created_atstring (date-time)Required
Time at which the webhook was created.
client_idstring
Client ID given at creation (idempotency key).
status"active" | "disabled"Required
active or disabled; mirrors enabled.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X POST "https://api.machinemail.dev/v0/webhooks" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/webhooks/machinemail",
  "event_types": [
    "message.received"
  ]
}'
Response 200
{
  "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
  "url": "https://example.com/webhooks/machinemail",
  "event_types": ["message.received"],
  "pod_ids": ["pod_01k6x2m9qd0001b8j4mr0w3d5f"],
  "inbox_ids": ["agent@machinemail.dev"],
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "secret": "whsec_0123456789abcdef",
  "enabled": true,
  "updated_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "status": "active"
}

Get Webhook

http
GET /v0/webhooks/{webhook_id}

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_read.

Path parameters

webhook_idstringRequired
ID of the webhook.

Response

200 The webhook (application/json)

webhook_idstringRequired
ID of the webhook.
urlstringRequired
URL of the webhook endpoint.
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]Required
Event types the webhook receives. Empty: every event type except domain.updated, which is sent only when listed.
pod_idsstring[]
Only events of these pods. Absent: every pod in scope.
inbox_idsstring[]
Only events of these inboxes. Absent: every inbox in scope.
pod_idstring
Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod.
inbox_idstring
Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox.
secretstringRequired
Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read.
enabledbooleanRequired
Whether new events are delivered. Webhooks are never disabled automatically.
updated_atstring (date-time)Required
Time at which the webhook was last updated.
created_atstring (date-time)Required
Time at which the webhook was created.
client_idstring
Client ID given at creation (idempotency key).
status"active" | "disabled"Required
active or disabled; mirrors enabled.

Errors

StatusMeaning
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl "https://api.machinemail.dev/v0/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
  "url": "https://example.com/webhooks/machinemail",
  "event_types": ["message.received"],
  "pod_ids": ["pod_01k6x2m9qd0001b8j4mr0w3d5f"],
  "inbox_ids": ["agent@machinemail.dev"],
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "secret": "whsec_0123456789abcdef",
  "enabled": true,
  "updated_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "status": "active"
}

Update Webhook

http
PATCH /v0/webhooks/{webhook_id}

Fields you omit are unchanged. Webhooks are never disabled automatically after failed deliveries; enabled: false is the only way to disable one. New event_types follow the same permission rule as POST /v0/webhooks, and changing url needs the read permission of every event type the webhook delivers (403 otherwise).

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_update.

Path parameters

webhook_idstringRequired
ID of the webhook.

Request body

Content type: application/json.

event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]
Replaces the subscribed event types in full (not a merge). An empty array subscribes to every event type except domain.updated, which is sent only when listed. Omit it to keep the types.
enabledboolean
false disables the webhook: no new deliveries are queued (deliveries already queued are still attempted). true enables it again. Events that occurred while it was disabled are not delivered later.
add_inbox_idsstring[]
Inboxes to add to inbox_ids.
remove_inbox_idsstring[]
Inboxes to remove from inbox_ids.
add_pod_idsstring[]
Pods to add to pod_ids.
remove_pod_idsstring[]
Pods to remove from pod_ids.
urlstring
New endpoint URL: a public http(s) URL.

Response

200 The updated webhook (application/json)

webhook_idstringRequired
ID of the webhook.
urlstringRequired
URL of the webhook endpoint.
event_types("message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test")[]Required
Event types the webhook receives. Empty: every event type except domain.updated, which is sent only when listed.
pod_idsstring[]
Only events of these pods. Absent: every pod in scope.
inbox_idsstring[]
Only events of these inboxes. Absent: every inbox in scope.
pod_idstring
Set when the webhook was created with a pod-scoped credential (or under /v0/pods/{pod_id}): it never receives events outside that pod.
inbox_idstring
Set when the webhook was created with an inbox-scoped credential (or under /v0/inboxes/{inbox_id}): it only receives events of that inbox.
secretstringRequired
Signing secret (whsec_...) for X-MachineMail-Signature. Returned on every read.
enabledbooleanRequired
Whether new events are delivered. Webhooks are never disabled automatically.
updated_atstring (date-time)Required
Time at which the webhook was last updated.
created_atstring (date-time)Required
Time at which the webhook was created.
client_idstring
Client ID given at creation (idempotency key).
status"active" | "disabled"Required
active or disabled; mirrors enabled.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X PATCH "https://api.machinemail.dev/v0/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_types": [
    "message.received"
  ],
  "url": "https://example.com/webhooks/machinemail"
}'
Response 200
{
  "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
  "url": "https://example.com/webhooks/machinemail",
  "event_types": ["message.received"],
  "pod_ids": ["pod_01k6x2m9qd0001b8j4mr0w3d5f"],
  "inbox_ids": ["agent@machinemail.dev"],
  "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
  "inbox_id": "agent@machinemail.dev",
  "secret": "whsec_0123456789abcdef",
  "enabled": true,
  "updated_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "client_id": "my-client-id",
  "status": "active"
}

Delete Webhook

http
DELETE /v0/webhooks/{webhook_id}

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_delete.

Path parameters

webhook_idstringRequired
ID of the webhook.

Response

200 Deleted (application/json)

oktrueRequired

Errors

StatusMeaning
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X DELETE "https://api.machinemail.dev/v0/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "ok": true
}

List Deliveries

http
GET /v0/webhooks/{webhook_id}/deliveries

Delivery attempts of the webhook, one per event (a replay is a new delivery).

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_read.

Path parameters

webhook_idstringRequired
ID of the webhook.

Query parameters

limitinteger
Maximum number of items to return. Default 50, maximum 500; larger values are capped.
page_tokenstring
next_page_token from the previous page.
ascendingboolean
Sort oldest first.
status"pending" | "delivered" | "failed"
Only deliveries with this status.

Response

200 A page of deliveries (application/json)

countintegerRequired
Number of items returned.
limitinteger
Maximum number of items per page. Default 50, maximum 500; larger values are capped.
next_page_tokenstring
Page token for pagination. Absent on the last page.
deliveriesWebhookDelivery[]Required
Ordered by created_at, newest first unless ascending is set.

Each item of deliveries is a WebhookDelivery:

delivery_idstringRequired
ID of the delivery (X-MachineMail-Delivery-Id).
webhook_idstringRequired
ID of the webhook.
event_idstringRequired
ID of the event (X-MachineMail-Event-Id).
event_type"message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test"Required
Type of event. AgentMail has no message.received.quarantined, domain.updated, inbox.created, inbox.expired, draft.sent or webhook.test; MachineMail does not emit message.opened or calendar.event.*. domain.updated is sent whenever a DNS check changes the domain status or a record status (the change to VERIFIED also sends domain.verified); webhooks and WebSocket subscriptions receive it only when their event_types list it, while the SSE stream and the event log always include it.
attemptintegerRequired
Number of attempts so far.
status"pending" | "delivered" | "failed"Required
pending until a 2xx response (delivered), or failed when retries stop (5 days after the event) or the webhook or event was deleted.
response_statusinteger
HTTP status of the last attempt.
response_bodystring
First 1024 bytes of the last response body.
next_attempt_atstring (date-time)
When the next attempt is due, while pending.
last_errorstring
Error of the last failed attempt (HTTP 500, a network error).
delivered_atstring (date-time)
When the delivery succeeded.
created_atstring (date-time)Required
When the delivery was queued.
updated_atstring (date-time)Required
Last change to the delivery.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl "https://api.machinemail.dev/v0/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k/deliveries" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "count": 1,
  "limit": 50,
  "next_page_token": "eyJvIjo1MH0",
  "deliveries": [
    {
      "delivery_id": "dlv_01k6x3f1ns0000j5s1vz7d0m2p",
      "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
      "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
      "event_type": "message.received",
      "attempt": 1,
      "status": "pending",
      "response_status": 1,
      "response_body": "string",
      "next_attempt_at": "2026-10-01T12:00:00.000Z",
      "last_error": "string",
      "delivered_at": "2026-10-01T12:00:00.000Z",
      "created_at": "2026-10-01T12:00:00.000Z",
      "updated_at": "2026-10-01T12:00:00.000Z"
    }
  ]
}

Replay Delivery

http
POST /v0/webhooks/{webhook_id}/deliveries/{delivery_id}/replay

Queues a new delivery of the same event to the webhook and attempts it right away, even when the webhook is disabled. Returns the new delivery after that attempt.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_update.

Path parameters

webhook_idstringRequired
ID of the webhook.
delivery_idstringRequired
ID of the delivery to replay.

Response

200 The new delivery (application/json)

delivery_idstringRequired
ID of the delivery (X-MachineMail-Delivery-Id).
webhook_idstringRequired
ID of the webhook.
event_idstringRequired
ID of the event (X-MachineMail-Event-Id).
event_type"message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test"Required
Type of event. AgentMail has no message.received.quarantined, domain.updated, inbox.created, inbox.expired, draft.sent or webhook.test; MachineMail does not emit message.opened or calendar.event.*. domain.updated is sent whenever a DNS check changes the domain status or a record status (the change to VERIFIED also sends domain.verified); webhooks and WebSocket subscriptions receive it only when their event_types list it, while the SSE stream and the event log always include it.
attemptintegerRequired
Number of attempts so far.
status"pending" | "delivered" | "failed"Required
pending until a 2xx response (delivered), or failed when retries stop (5 days after the event) or the webhook or event was deleted.
response_statusinteger
HTTP status of the last attempt.
response_bodystring
First 1024 bytes of the last response body.
next_attempt_atstring (date-time)
When the next attempt is due, while pending.
last_errorstring
Error of the last failed attempt (HTTP 500, a network error).
delivered_atstring (date-time)
When the delivery succeeded.
created_atstring (date-time)Required
When the delivery was queued.
updated_atstring (date-time)Required
Last change to the delivery.

Errors

StatusMeaning
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X POST "https://api.machinemail.dev/v0/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k/deliveries/dlv_01k6x3f1ns0000j5s1vz7d0m2p/replay" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "delivery_id": "dlv_01k6x3f1ns0000j5s1vz7d0m2p",
  "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
  "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
  "event_type": "message.received",
  "attempt": 1,
  "status": "pending",
  "response_status": 1,
  "response_body": "string",
  "next_attempt_at": "2026-10-01T12:00:00.000Z",
  "last_error": "string",
  "delivered_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z"
}

Send Test Event

http
POST /v0/webhooks/{webhook_id}/test

Records a webhook.test event and delivers it to this webhook only (whatever its event_types, even when disabled), then returns the delivery after the first attempt.

Authentication: Authorization: Bearer <api key> or the dashboard mm_session cookie. Permission: webhook_update.

Path parameters

webhook_idstringRequired
ID of the webhook.

Response

200 The test delivery (application/json)

delivery_idstringRequired
ID of the delivery (X-MachineMail-Delivery-Id).
webhook_idstringRequired
ID of the webhook.
event_idstringRequired
ID of the event (X-MachineMail-Event-Id).
event_type"message.received" | "message.received.spam" | "message.received.quarantined" | "message.received.unauthenticated" | "message.received.blocked" | "message.sent" | "message.delivered" | "message.bounced" | "message.complained" | "message.rejected" | "domain.verified" | "domain.updated" | "inbox.created" | "inbox.expired" | "draft.sent" | "webhook.test"Required
Type of event. AgentMail has no message.received.quarantined, domain.updated, inbox.created, inbox.expired, draft.sent or webhook.test; MachineMail does not emit message.opened or calendar.event.*. domain.updated is sent whenever a DNS check changes the domain status or a record status (the change to VERIFIED also sends domain.verified); webhooks and WebSocket subscriptions receive it only when their event_types list it, while the SSE stream and the event log always include it.
attemptintegerRequired
Number of attempts so far.
status"pending" | "delivered" | "failed"Required
pending until a 2xx response (delivered), or failed when retries stop (5 days after the event) or the webhook or event was deleted.
response_statusinteger
HTTP status of the last attempt.
response_bodystring
First 1024 bytes of the last response body.
next_attempt_atstring (date-time)
When the next attempt is due, while pending.
last_errorstring
Error of the last failed attempt (HTTP 500, a network error).
delivered_atstring (date-time)
When the delivery succeeded.
created_atstring (date-time)Required
When the delivery was queued.
updated_atstring (date-time)Required
Last change to the delivery.

Errors

StatusMeaning
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X POST "https://api.machinemail.dev/v0/webhooks/whk_01k6x3c2jp0000f2p8rw4a7h9k/test" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response 200
{
  "delivery_id": "dlv_01k6x3f1ns0000j5s1vz7d0m2p",
  "webhook_id": "whk_01k6x3c2jp0000f2p8rw4a7h9k",
  "event_id": "evt_01k6x3g4pt0000k6t2wa8e1n3q",
  "event_type": "message.received",
  "attempt": 1,
  "status": "pending",
  "response_status": 1,
  "response_body": "string",
  "next_attempt_at": "2026-10-01T12:00:00.000Z",
  "last_error": "string",
  "delivered_at": "2026-10-01T12:00:00.000Z",
  "created_at": "2026-10-01T12:00:00.000Z",
  "updated_at": "2026-10-01T12:00:00.000Z"
}