Webhooks
Webhooks are how Mailtarget tells your application what happened after a message left the platform. Submit returns a transmissionId. Webhooks return delivery, engagement, and failure events tied to that ID.
If you skip webhooks, you have no visibility into what happened to the email after the API returned a 2xx.
Event types
Mailtarget fires events for the lifecycle of every message.
| Event | When it fires |
|---|---|
delivery | The receiving server accepted the message. |
bounce | The receiving server permanently rejected the message (hard bounce). |
soft_bounce | The receiving server temporarily rejected the message. Mailtarget retries internally before this event fires as final. |
open | The recipient opened the message and the tracking pixel loaded. |
click | The recipient clicked a tracked link. |
complaint | The recipient marked the message as spam in their mail client. |
unsubscribe | The recipient clicked an unsubscribe link. |
out_of_band | The receiving server reported a delivery state change after the initial accept (rare; happens when an upstream server bounces a message that was already accepted at the gateway). |
Consult the dashboard for the complete event catalog and any account-specific events.
Payload shape
Every event payload includes a stable set of fields.
{
"transmissionId": "abc123",
"messageId": "<mailtarget-internal-message-id>",
"event": "delivery",
"timestamp": "2026-05-08T03:14:15Z",
"recipient": "user@example.com",
"campaignId": "<your-metadata-or-null>",
"metadata": { "any": "metadata you set on submit" },
"raw": { "event-specific fields like bounce reason, click URL, etc." }
}
The transmissionId matches the value returned at submit. The metadata object echoes what you set on the request body. Use metadata to carry your own correlation fields (order ID, user ID, campaign code) so you do not have to maintain a separate join table.
The exact field names and the raw shape per event are in the dashboard webhook documentation. Treat the dashboard as the source of truth and tag this page with review_needed: true until the field-level mapping is confirmed.
Signature verification
Mailtarget signs every webhook request. Verify the signature before trusting the payload.
The signature is a header on the HTTP request. Recompute the signature on your end with the webhook secret (visible in the dashboard at webhook configuration time), compare with constant-time equality, and reject the request if the comparison fails.
import hmac, hashlib
def verify(body_bytes, header_signature, secret):
expected = hmac.new(secret.encode(), body_bytes, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header_signature)
Constant-time comparison matters. A regular string equality check leaks the secret to a timing attacker who can spam your endpoint and measure response times.
The exact header name and the canonical message format used to compute the signature are in the dashboard webhook documentation. Tag any divergence as review_needed: true and confirm with the engineering team before shipping verification code to production.
Retries
Mailtarget retries webhooks if your endpoint does not respond with a 2xx status code within a bounded time. Default policy:
- Retry on any non-2xx response or timeout.
- Exponential backoff with multiple attempts spread over hours.
- After the retry window expires, the event is dropped from the active queue and visible in the dashboard delivery log for manual replay.
Two consequences for your endpoint:
- Respond fast. Acknowledge receipt with
200 OKimmediately, then process asynchronously. If your handler runs synchronously and the work takes 30 seconds, you will time out the webhook and trigger retries you did not want. - Be idempotent. Mailtarget will deliver the same event more than once during a retry storm. Dedupe on
transmissionIdpluseventplustimestamp(or a stable event ID if your dashboard exposes one).
Idempotency
Two events with the same transmissionId and event and timestamp are the same event. Process them once.
A common pattern:
- On webhook receipt, compute a key from
transmissionId + event + timestamp. - Insert into a dedupe table with that key as the unique constraint.
- If the insert fails on uniqueness, the event has already been processed. Return 200 and stop.
- If the insert succeeds, do the work.
This pattern survives retry storms and survives the case where your application processes the event but crashes before responding 200. Mailtarget will retry, the dedupe will catch the second delivery, and the application stays consistent.
Configuring webhooks
Webhooks are configured per account in the dashboard. You set:
- The destination URL.
- The events you want delivered.
- The signing secret.
- Optional filters (per campaign, per metadata field).
Use one URL per logical handler. If you have a service that cares only about delivery and bounce and a separate service that handles open and click, register two webhooks.
Operations
For production webhook reliability (timeout budgets, replay flow, debugging delivery failures), see the Operations section on webhook reliability.