DocsWebhooks ▾
IntroductionQuickstartAuthenticationSending emailDomainsAPI keysSMTP relayWebhooksTemplatesBroadcastsAudiences & contactsSuppression listOpen & click trackingIP warm-upDeliverabilityAnalytics & logsLimits & errorsSelf-hosting & configurationEmailsDomainsAudiences & contactsBroadcastsTemplatesWebhooksAPI keys, suppressions, metrics
Guides
Webhooks
Register an HTTPS endpoint and choose which events to receive. Each delivery is a JSON POST.
Events
| Event | Meaning |
|---|---|
email.sent | Accepted by Mailvina and queued |
email.scheduled | Accepted for future delivery |
email.delivered | Accepted by the recipient’s mail server |
email.delivery_delayed | Temporary failure, retrying |
email.bounced | Permanently rejected |
email.complained | Recipient marked it as spam |
email.opened / email.clicked | Engagement tracking |
email.suppressed | Skipped because the address is suppressed |
domain.*, contact.* | Resource changes |
Payload
{
"type": "email.bounced",
"created_at": "2026-11-14T10:00:00.000Z",
"data": {
"email_id": "49a3999c-…",
"from": "Acme <[email protected]>",
"to": [
"[email protected]"
],
"subject": "Welcome",
"recipient": "[email protected]",
"bounced": {
"type": "permanent",
"message": "550 5.1.1 Mailbox does not exist"
}
}
}Verify signatures
Each request carries mailvina-id, mailvina-timestamp and mailvina-signature. The signature is v1, followed by the base64 HMAC-SHA256 of {id}.{timestamp}.{body} using your signing secret (decoded from base64 after the whsec_ prefix).
import crypto from 'node:crypto';
export function verify(rawBody, headers, secret) {
const id = headers['mailvina-id'];
const ts = headers['mailvina-timestamp'];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // replay protection
const key = Buffer.from(secret.replace('whsec_', ''), 'base64');
const expected = 'v1,' + crypto.createHmac('sha256', key).update(`${id}.${ts}.${rawBody}`).digest('base64');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(headers['mailvina-signature']));
}Retries
Respond with any 2xx within 10 seconds. Failed deliveries retry after 30s, 5m, 30m, 2h, 5h and 10h.