POST to it when something happens in your workspace, such as a message being sent, delivered, opened, or failing, a template being published, or an audience changing.
For the opposite direction, where your systems send events to Courier, see Inbound Webhooks.
Adding a New Webhook Destination
- Go to Settings > Webhooks, under Developers, and find the Outbound Webhooks section.
- Click Add.
- Give the destination a name and enter its URL. The endpoint needs to accept a
POSTrequest.
Handling Requests from Courier
Read the Event Data
Courier sends the event data in the request body. Each event is structured as an object with atype property and related resource data nested under the data property.
Handle the Event
Handle events based on thetype property in the payload. Additional event types may be added in the future, so your handler should gracefully ignore unknown types.
Return a 2xx Response
Courier gives your endpoint 10 seconds to respond. Acknowledge the event first and do the real work afterwards, in a queue or background job, so a slow dependency doesn’t turn into a delivery failure.Delivery and Retries
Courier retries a delivery when the failure looks temporary:
Retries continue for roughly a day before Courier gives up. An event can therefore arrive more than once, so make your handler idempotent. Note that
data.id identifies the resource rather than the event, so a single message produces several events that share it.
Verifying Signatures
Set a secret on the webhook and Courier signs every event it sends to that destination, adding acourier-signature header. Verifying it confirms an event came from Courier and not a third party. Without a secret, no header is sent.
t is the time Courier signed the event, in milliseconds since the epoch. signature is an HMAC-SHA256 digest, hex encoded.
Get the secret from Webhooks settings by opening the webhook configuration. You need it as the HMAC key.
Step 1: Extract the Timestamp and Signature from the Header
Split the header on, to get a list of elements, then split each element on = to get a prefix and value pair.
The value for the prefix t is the timestamp, and signature is the signature.
Step 2: Prepare the signed_payload String
Concatenate the timestamp, a . separator, and the raw request body.
Step 3: Determine the Expected Signature
Compute an HMAC with the SHA256 hash function, using the webhook secret as the key and thesigned_payload string as the message.
Step 4: Compare the Signatures
Compare your computed signature to the one in the header with a constant-time comparison, so a mismatch doesn’t leak information through timing. Check the timestamp separately. Reject events whose timestamp falls outside a tolerance window you choose, which limits how long a captured request stays replayable.Event Data
message:updated
Each message status change triggers amessage:updated webhook event. Delivery statuses like DELIVERED depend on your email provider asynchronously confirming delivery, while engagement statuses like OPENED and CLICKED are tracked by Courier directly and fire as soon as the recipient acts. This means you may receive engagement events before delivery confirmation, or without it arriving at all.
For the full status lifecycle diagram and definitions, see Message Statuses.
The data property in the webhook response payload for the message:updated event is identical to the information returned from the GET /message/:message_id endpoint.
Any metadata associated with the message is included in the data property. This includes:
trace_idtagseventutm
CLICKED event still carries enqueued, sent, delivered, and opened. Here’s a DELIVERED event:
data: