Skip to content
Docs

Register endpoints that receive signed event notifications.

Base URLhttps://api.emailit.com/v2AuthenticationErrorsRate limits

Create a webhook

Creates a webhook endpoint in your workspace. Emailit sends matching events to the URL in batches of up to 100, as a JSON array signed with the webhook’s secret. See Webhook requests for the request format. Requires an API key with full scope.

POST/webhooks

Request body

namestringRequired

Webhook name. Must be unique in the workspace; you can use it instead of the ID in other webhook endpoints.

urlstringRequired

Endpoint that receives the events. http and https URLs are accepted; use https in production.

Emailit resolves the hostname when you save and rejects localhost, private, link-local and other reserved IP addresses. Redirects aren’t followed when delivering, so use the final URL.

all_eventsboolean

Send every event type, including types added later. Default false. When true, events is ignored.

enabledboolean

Whether Emailit delivers events to the webhook. Default true.

eventsstring[]

Event types to send, for example ["email.delivered", "email.bounced"]. See Event types. Default [], which with all_events: false means the webhook receives nothing.

Event names aren’t validated. A misspelled type is saved but never matches an event.

filterobject | null

Payload filter. Emailit only sends events whose object matches the rules. Available on Pro, Business and Custom plans; a filter with rules on Pay as you go returns 403.

filter.matchstring

all (default) sends an event when every rule matches. any sends it when at least one rule matches.

filter.rulesobject[]

Up to 25 rules.

filter.rules[].fieldstringRequired

Dotted path into the event object, for example to, status, meta.plan or, for click and open events, email.campaign.id. A leading payload. is ignored.

filter.rules[].operatorstringRequired

equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than, is_set, is_not_set, in or not_in. Text operators compare values as strings; greater_than and less_than compare numbers.

filter.rules[].valueany

Value to compare with. Required for every operator except is_set and is_not_set. Use an array with in and not_in.

Returns

Returns 201 Created with the webhook object, including the signing secret (whsec_ followed by 64 hex characters). Use the secret to verify request signatures. You can read it again with Retrieve a webhook and rotate it with Rotate the signing secret.

Status When
400 name or url is missing, the URL is invalid, can’t be resolved or points to a blocked address, or the filter is invalid.
403 The filter has rules and your plan doesn’t include webhook filters. The body is {"error": "plan_required", "required_plan": "pro"}.
409 A webhook with this name already exists. The body includes the existing webhook’s id and name.
422 The workspace has reached its plan’s webhook limit. The body includes usage.used and usage.limit. See Limits.
POST/webhooks
Terminal
curl -X POST https://api.emailit.com/v2/webhooks \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production events",
    "url": "https://api.acme.com/webhooks/emailit",
    "events": ["email.delivered"]
  }'
JSON
{
  "object": "webhook",
  "id": "wh_2xLb3Kp9Qr1Vt7Mn5Ws0Yd4Hc8e",
  "name": "Production events",
  "url": "https://api.acme.com/webhooks/emailit",
  "all_events": false,
  "enabled": true,
  "events": ["email.delivered", "email.bounced"],
  "filter": null,
  "filters_allowed": true,
  "last_used_at": null,
  "created_at": "2026-10-01T09:41:05.302000+00:00",
  "updated_at": "2026-10-01T09:41:05.302000+00:00",
  "secret": "whsec_175a02aca3fb7dc3107ca21e4224a73d058cacc628356a39e020422aec3ca434"
}
JSON
{
  "name": "Enterprise bounces",
  "url": "https://api.acme.com/webhooks/emailit",
  "events": ["email.bounced", "email.complained"],
  "filter": {
    "match": "all",
    "rules": [
      { "field": "meta.plan", "operator": "equals", "value": "enterprise" },
      { "field": "to", "operator": "not_contains", "value": "@acme.com" }
    ]
  }
}

Retrieve a webhook

Returns one webhook, looked up by ID or by name. This is the only read endpoint that returns the signing secret. Requires an API key with full scope.

GET/webhooks/:id

Path parameters

idstringRequired

Webhook ID (wh_…) or the webhook’s name, URL-encoded.

Returns

Returns 200 OK with the webhook object, including secret and filters_allowed (whether your plan lets the webhook use a payload filter). last_used_at is the time of the last successful delivery, or null if nothing has been delivered yet.

Returns 404 with error: "Webhook not found" if no webhook matches.

GET/webhooks/{id}
Terminal
curl https://api.emailit.com/v2/webhooks/wh_2xLb3Kp9Qr1Vt7Mn5Ws0Yd4Hc8e \
  -H "Authorization: Bearer your_api_key"
JSON
{
  "object": "webhook",
  "id": "wh_2xLb3Kp9Qr1Vt7Mn5Ws0Yd4Hc8e",
  "name": "Production events",
  "url": "https://api.acme.com/webhooks/emailit",
  "all_events": false,
  "enabled": true,
  "events": ["email.delivered", "email.bounced"],
  "filter": {
    "match": "all",
    "rules": [
      { "field": "meta.plan", "operator": "equals", "value": "enterprise" }
    ]
  },
  "filters_allowed": true,
  "last_used_at": "2026-10-01T10:02:17.845000+00:00",
  "created_at": "2026-10-01T09:41:05.302000+00:00",
  "updated_at": "2026-10-01T09:41:05.302000+00:00",
  "secret": "whsec_175a02aca3fb7dc3107ca21e4224a73d058cacc628356a39e020422aec3ca434"
}

Update a webhook

Updates a webhook. Send only the fields you want to change; at least one is required. The signing secret doesn’t change; rotate it with Rotate the signing secret. Requires an API key with full scope.

POST/webhooks/:id

Path parameters

idstringRequired

Webhook ID (wh_…) or the webhook’s name, URL-encoded.

Request body

namestring

New name. Must be unique in the workspace.

urlstring

New endpoint URL, http or https. Validated the same way as on create.

all_eventsboolean

true sends every event type and clears the events list. If you set it to false, also send events, or the webhook receives nothing.

enabledboolean

false stops deliveries and true resumes them. Events that happen while the webhook is disabled aren’t queued for it and aren’t sent later.

eventsstring[]

Replaces the list of event types. Ignored while all_events is true. Names aren’t validated.

filterobject | null

Replaces the payload filter, in the same format as on create. Send null to remove it. A filter with rules needs a Pro, Business or Custom plan.

Returns

Returns 200 OK with the updated webhook. The secret isn’t included; use Retrieve a webhook to read it.

Status When
400 The body has none of the fields above, or the URL or filter is invalid.
403 The filter has rules and your plan doesn’t include webhook filters (plan_required).
404 No webhook matches id.
409 Another webhook already uses the new name.
POST/webhooks/{id}
Terminal
curl -X POST https://api.emailit.com/v2/webhooks/wh_2xLb3Kp9Qr1Vt7Mn5Ws0Yd4Hc8e \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"enabled": false}'
JSON
{
  "object": "webhook",
  "id": "wh_2xLb3Kp9Qr1Vt7Mn5Ws0Yd4Hc8e",
  "name": "Production events",
  "url": "https://api.acme.com/webhooks/emailit",
  "all_events": false,
  "enabled": false,
  "events": ["email.delivered", "email.bounced"],
  "filter": null,
  "filters_allowed": true,
  "last_used_at": "2026-10-01T10:02:17.845000+00:00",
  "created_at": "2026-10-01T09:41:05.302000+00:00",
  "updated_at": "2026-10-01T10:15:40.000000+00:00"
}

List webhooks

Returns the webhooks in your workspace, newest first, and how many your plan allows. Signing secrets aren’t included in the list. Requires an API key with full scope.

GET/webhooks

Query parameters

pageinteger

Page number, starting at 1. Default 1.

limitinteger

Webhooks per page, from 1 to 100. Default 10.

searchstring

Case-insensitive match on the webhook name or URL.

matchstring

all (default) requires every filter. or matches any filter. See Filtering.

orderstring

Sort key for this list. See the sort keys below.

directionstring

asc or desc.

Filters and sort

List filters are one layer of key.condition=value query parameters. See Filtering for match, order, direction and the condition list per type.

Filter keys

KeyTypeConditionsNotes
namestringexact, not_exact, contains, not_contains, starts_with, ends_with, empty, not_empty
urlstringexact, not_exact, contains, not_contains, starts_with, ends_with, empty, not_empty
enabledbooleanexact, not_exact
created_atdateexact, before, after, empty, not_empty

Sort keys

Pass order as one of these keys and direction as asc or desc: name, url, enabled, created_at

Returns

Returns 200 OK with the webhooks in data, next_page_url and previous_page_url (null at either end), and a usage object: used is the number of webhooks in the workspace, limit is your plan’s maximum, and filters_allowed says whether your plan includes payload filters.

GET/webhooks
Terminal
curl https://api.emailit.com/v2/webhooks \
  -H "Authorization: Bearer your_api_key"
JSON
{
  "data": [
    {
      "object": "webhook",
      "id": "wh_2xLb3Kp9Qr1Vt7Mn5Ws0Yd4Hc8e",
      "name": "Production events",
      "url": "https://api.acme.com/webhooks/emailit",
      "all_events": false,
      "enabled": true,
      "events": ["email.delivered", "email.bounced"],
      "filter": null,
      "filters_allowed": true,
      "last_used_at": "2026-10-01T10:02:17.845000+00:00",
      "created_at": "2026-10-01T09:41:05.302000+00:00",
      "updated_at": "2026-10-01T10:02:17.845000+00:00"
    }
  ],
  "next_page_url": null,
  "previous_page_url": null,
  "usage": {
    "used": 1,
    "limit": 10,
    "filters_allowed": true
  }
}

Delete a webhook

Permanently deletes a webhook and its event subscriptions. To stop deliveries temporarily, update the webhook with enabled: false instead. Requires an API key with full scope.

DELETE/webhooks/:id

Path parameters

idstringRequired

Webhook ID (wh_…) or the webhook’s name, URL-encoded.

Returns

Returns 200 OK with the deleted webhook’s id and name and deleted: true. Returns 404 with error: "Webhook not found" if no webhook matches.

DELETE/webhooks/{id}
Terminal
curl -X DELETE https://api.emailit.com/v2/webhooks/wh_2xLb3Kp9Qr1Vt7Mn5Ws0Yd4Hc8e \
  -H "Authorization: Bearer your_api_key"
JSON
{
  "object": "webhook",
  "id": "wh_2xLb3Kp9Qr1Vt7Mn5Ws0Yd4Hc8e",
  "name": "Production events",
  "deleted": true
}

Send a test event

Sends a sample event of the type you choose to the webhook’s URL and returns your endpoint’s response. Use it to check that your endpoint is reachable and verifies signatures correctly. Requires an API key with the full scope.

The request has the same format, headers and signature as a real delivery: a JSON array with one event whose event_id starts with evt_test_, signed with the webhook’s current secret. It’s sent even if the webhook is disabled or not subscribed to that type, isn’t stored as a webhook request, and isn’t retried. The sample data is fixed and doesn’t refer to real objects.

You can send 5 test events per minute from the same IP address; more return 429.

POST/webhooks/{id}/test

Path parameters

idstringrequired
The webhook ID (wh_…) or the webhook’s name.

Body parameters

typestringrequired
The event type to send. One of the types below.
Resource Event types
Email email.accepted, email.scheduled, email.delivered, email.bounced, email.attempted, email.failed, email.rejected, email.clicked, email.loaded, email.complained, email.received, email.suppressed, email.canceled, email.unsubscribed, email.resubscribed
Domain domain.created, domain.updated, domain.deleted
Audience audience.created, audience.updated, audience.deleted
Subscriber subscriber.created, subscriber.updated, subscriber.deleted
Contact contact.created, contact.updated, contact.deleted
Template template.created, template.updated, template.deleted
Suppression suppression.created, suppression.updated, suppression.deleted
Email verification email_verification.created, email_verification.updated, email_verification_list.created, email_verification_list.updated
Campaign campaign.created, campaign.updated, campaign.deleted, campaign.scheduled, campaign.queued, campaign.sending, campaign.testing, campaign.sent, campaign.canceled, campaign.archived

See Event types for what each event means.

Returns

okboolean
true if your endpoint answered with a 2xx status.
status_codeinteger
Your endpoint’s HTTP status. 0 if Emailit couldn’t connect, the request timed out after 30 seconds, the endpoint redirected (redirects aren’t followed), or the URL points to a blocked address.
bodystring
The first 2,000 characters of your endpoint’s response, or the connection error.
typestring
The event type sent.
payloadobject[]
The exact JSON array that was sent.

Returns 400 if type is missing or unknown, 404 if the webhook doesn’t exist, and 429 when you exceed the test limit.

POST/webhooks/{id}/test
Terminal
curl -X POST https://api.emailit.com/v2/webhooks/wh_3NGa6raJ3s1VJ75lO9km6Dqg4cQ/test \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "email.delivered" }'
JSON
{
  "ok": true,
  "status_code": 200,
  "type": "email.delivered",
  "payload": [
    {
      "event_id": "evt_test_3G0pUekgBm1uIxi9m4C380H70Zq",
      "type": "email.delivered",
      "object": {
        "id": "eml_test_001",
        "email_id": 12345,
        "message_id": "<test-token@mydomain.com>",
        "from": "sender@mydomain.com",
        "to": "recipient@example.com",
        "subject": "Test email",
        "status": "delivered",
        "delivered_at": "2026-01-15T10:30:00.000Z"
      },
      "data": {
        "object": {
          "id": "eml_test_001",
          "email_id": 12345,
          "message_id": "<test-token@mydomain.com>",
          "from": "sender@mydomain.com",
          "to": "recipient@example.com",
          "subject": "Test email",
          "status": "delivered",
          "delivered_at": "2026-01-15T10:30:00.000Z"
        }
      }
    }
  ],
  "body": "{\"received\":true}"
}

Rotate the signing secret

Generates a new signing secret for the webhook and returns it. Requires an API key with the full scope.

The old secret stops being used immediately: every request sent after the rotation, including retries of earlier events, is signed with the new secret. There’s no overlap period, so update the secret in your endpoint right after rotating, or accept both secrets for a short time while you switch. See Verify request signatures.

POST/webhooks/{id}/reset-secret

Path parameters

idstringrequired
The webhook ID (wh_…) or the webhook’s name.

Returns

Returns the webhook object with the new secret (whsec_ followed by 64 hexadecimal characters). Returns 404 if the webhook doesn’t exist.

POST/webhooks/{id}/reset-secret
Terminal
curl -X POST https://api.emailit.com/v2/webhooks/wh_3NGa6raJ3s1VJ75lO9km6Dqg4cQ/reset-secret \
  -H "Authorization: Bearer $EMAILIT_API_KEY"
JSON
{
  "object": "webhook",
  "id": "wh_3NGa6raJ3s1VJ75lO9km6Dqg4cQ",
  "name": "Order notifications",
  "url": "https://acme.com/webhooks/emailit",
  "all_events": false,
  "enabled": true,
  "events": ["email.delivered", "email.bounced", "email.complained"],
  "filter": null,
  "last_used_at": "2026-10-01T12:58:40.000000+00:00",
  "created_at": "2026-08-14T09:12:03.000000+00:00",
  "updated_at": "2026-10-01T13:20:11.000000+00:00",
  "secret": "whsec_a0a593d006bdec5aff2f21e88afd543ba239431acbe7d8dc7a9723d76e0a64e2"
}

Retry failed requests

Queues every request of this webhook that permanently failed in the last 7 days for delivery again. Requires an API key with the full scope.

A request fails permanently after its last automatic retry (11 attempts over several days; see Retries and failures). Retried requests start over with a full retry schedule and are delivered within seconds. If at least one request is queued and the webhook was disabled, for example after 3 days of continuous failures, it’s enabled again.

Fix your endpoint first, or the requests fail again. To retry a single request, use Retry one request.

POST/webhooks/{id}/retry-failed

Path parameters

idstringrequired
The webhook ID (wh_…) or the webhook’s name.

Returns

retriedinteger
Number of requests queued again. 0 if there was nothing to retry; the webhook’s enabled state doesn’t change then.

Returns 404 if the webhook doesn’t exist.

POST/webhooks/{id}/retry-failed
Terminal
curl -X POST https://api.emailit.com/v2/webhooks/wh_3NGa6raJ3s1VJ75lO9km6Dqg4cQ/retry-failed \
  -H "Authorization: Bearer $EMAILIT_API_KEY"
JSON
{
  "retried": 37
}

Retry one request

Queues one permanently failed webhook request for delivery again, with a fresh retry schedule. If the webhook was disabled, it’s enabled again. Requires an API key with the full scope.

Only requests that have exhausted their automatic retries can be retried this way; requests that are still pending or retrying return 400. Find request IDs (whr_…) on the webhook’s Requests tab under Email APIWebhooks. To retry everything from the last 7 days at once, use Retry failed requests.

POST/webhooks/{id}/requests/{request_id}/retry

Path parameters

idstringrequired
The webhook ID (wh_…) or the webhook’s name.
request_idstringrequired
The webhook request ID (whr_…).

Returns

retriedinteger
Always 1.
idstring
The request ID that was queued.
Status When
400 The request hasn’t permanently failed, or it has no event to resend.
404 The webhook doesn’t exist, or the request doesn’t belong to it.
POST/webhooks/{id}/requests/{request_id}/retry
Terminal
curl -X POST https://api.emailit.com/v2/webhooks/wh_3NGa6raJ3s1VJ75lO9km6Dqg4cQ/requests/whr_3tWVVMd2eHv3R9B5DWTLtwwU05X/retry \
  -H "Authorization: Bearer $EMAILIT_API_KEY"
JSON
{
  "retried": 1,
  "id": "whr_3tWVVMd2eHv3R9B5DWTLtwwU05X"
}

Was this page helpful?

Thanks for the feedback.

Thanks, we read every message.