Webhooks

Create an endpoint that receives Workwae events as signed HTTPS POSTs, and choose which events it gets.

A webhook endpoint is an HTTPS URL that Workwae POSTs to for every event it subscribes to: a message arrives, a send completes, an account needs reauthorization. This page covers creating an endpoint, filtering its events and the request it receives.

Webhooks push events as they happen, signed and retried. The change feed carries the same events, with the same ids, for you to poll instead.

Create an endpoint

Create the endpoint

Use the console's Webhooks page or the API.

curl -X POST "https://api.workwae.com/v1/webhooks" \
  -H "Authorization: Bearer $WORKWAE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
  "url": "https://app.example.com/hooks/workwae",
  "description": "Production",
  "filters": { "eventTypes": ["message.created", "account.reauth_required"] }
}'

Store the signing secret

The response carries the endpoint's signing secret (whsec_...) this once only. Rotating it issues a new one.

Verify and acknowledge deliveries

Verify each signature, then answer 2xx within 10 seconds. Send a test event to check your receiver before real traffic exists.

Endpoint URL requirements

  • The URL must use HTTPS; any other URL answers 400 with reason https_required.
  • It must resolve to a public address. Deliveries to private, loopback and link-local networks are refused.

Filters

An endpoint's filters object decides which events it receives. Omitted filters are unset on create and left as they are on update.

FilterDeliversDefault
eventTypesOnly these event types.Empty: every type, including types added later.
accountIdsOnly events of these accounts. Once set, events that concern no account (such as account.purged) are not delivered either. Ids must be accounts of your tenant, else 422 with reason unknown_account.Empty: every account.
messageOnly events about matching messages; see message criteria.Unset.
  • Change filters at any time with PATCH /v1/webhooks/{endpointId} and a filters object holding only the filters to change. Each list takes up to 100 entries.
  • Filters do not apply to test events or to a replay of one picked event. A replay of a time range applies them all.

Message criteria

filters.message narrows message.*, send.* and message.bounced events to some messages; every other event passes. Every criterion you set must match, and any one value within a criterion is enough. Criteria are matched against the event's own data, so a criterion the event cannot answer does not match.

CriterionMatches when
mailboxIds, mailboxRolesOne criterion: the message is in a listed mailbox, or in a mailbox with a listed role (inbox, sent, ...).
fromThe sender is a listed address, or at a listed @example.com domain (exact domain, case-insensitive).
toA to or cc recipient matches, compared like from.
subjectContainsA listed phrase appears anywhere in the subject, case-insensitive.
hasAttachmentstrue or false; null clears it.
directionoutbound: the message is in the sent mailbox or from one of the account's own addresses (its address and identities). inbound: every other message. null clears it.
  • Mailbox ids must be live mailboxes of your tenant (422, reason unknown_mailbox) and, when filters.accountIds is set, of those accounts (400, reason mailbox_outside_accounts).
  • message.deleted carries the message as last indexed and the mailboxes that held it, and is matched on those.
  • send.* events name the queue entry, not the message: they are outbound and answer no other criterion.
  • message.bounced is outbound, and to is matched against its failed recipients.

What your endpoint receives

Each delivery is a POST with a JSON body. Answer with any 2xx status within 10 seconds to acknowledge it. Redirects are not followed: a 3xx counts as a failure, and failures are retried.

HeaderValue
webhook-idThe event id, the same as the body's id. Stable across retries and replays: deduplicate on it.
webhook-timestampWhen this attempt was signed, in Unix seconds.
webhook-signatureOne or more space-separated v1,<base64> signatures.
content-typeapplication/json
user-agentworkwae-webhooks/1
Body
{
  "id": "1001",
  "type": "message.created",
  "accountId": "6f1c2a4e-8b3d-4e5f-9a7b-1c2d3e4f5a6b",
  "createdAt": "2026-05-04T09:30:00.000Z",
  "data": { "message": { "id": "c4e6a8b0-…", "subject": "Quarterly report" }, "mailboxes": [] }
}
  • data depends on type; the event catalog has a full sample for each.
  • accountId is the account the event concerns; data does not repeat it. It is null for events that concern no account.
  • Test events add "isTest": true.

Process asynchronously

Acknowledge quickly and do the work in a queue of your own. Retries and replays deliver the same event id again, so make handling idempotent on webhook-id.

On this page