Webhooks introduction
Webhooks are how 3AVA Mail tells your app what happened: an email was delivered, a contact opened it, a campaign auto-paused. They are the alternative to polling GET /emails/{id} repeatedly.
How it works
Section titled “How it works”- You register a webhook with a URL and a list of event types.
- 3AVA Mail saves a
signing_secret(returned once at creation). - When a subscribed event fires, 3AVA Mail POSTs a JSON payload to your URL with an HMAC signature header.
- Your app verifies the signature, processes the payload, and returns 2xx.
- Non-2xx responses trigger retries with exponential backoff.
The payload shape
Section titled “The payload shape”{ "type": "email.delivered", "created_at": "2026-04-22T22:30:04Z", "data": { "email_id": "a1b2...", "subject": "Your receipt", "delivered_at": "2026-04-22T22:30:04Z" }}The shape of data depends on the event type. See Event types.
Signature header
Section titled “Signature header”Every request includes:
X-3AVA-Signature: t=1714000000,v1=5d9f...t is the Unix timestamp the signature was generated. v1 is the HMAC-SHA256 of {t}.{body} using your signing_secret.
Retries
Section titled “Retries”If your endpoint returns non-2xx (or times out after 10s), 3AVA Mail retries:
| Attempt | Delay |
|---|---|
| 1st retry | 1s |
| 2nd retry | 4s |
| 3rd retry | 16s |
| 4th retry | 1m |
| 5th retry | 5m |
After 5 failed attempts, the delivery is marked failed and visible in the dashboard. You can re-trigger from there manually.
Idempotency
Section titled “Idempotency”Each event includes a unique event_id. Use it to dedupe in your handler — retries deliver the same payload with the same ID. Store the IDs you’ve processed (1-day TTL is enough).
Best practices
Section titled “Best practices”- Respond fast. Acknowledge the request immediately (200) and process asynchronously. Webhook timeouts cause retries.
- Verify the signature. Without verification, anyone who knows your URL can spoof events.
- Don’t expose secrets in error messages. If signature verification fails, log it but return 200 — otherwise you signal to attackers that your endpoint exists.