Get notified when something happens (not live yet)
Subscribe an endpoint to email events, and verify that a delivery really came from us.
Webhooks are how your application will receive notifications when email events occur — deliveries, opens, clicks, bounces. You can register endpoints and test them today; NevarMail does not yet dispatch events from real activity, so nothing arrives at them until that ships (see the callout under Event types).
Overview
NevarMail supports both inbound routing (processing emails your account receives) and outbound event webhooks (notifications about email events, delivered to endpoints you register).
For inbound email processing, see Inbound Routing.
Event webhooks
Register one or more webhook endpoints and NevarMail will fan out matching events to each of them in real time. This also lets you work around a provider's own webhook-count limit: register as many NevarMail endpoints as you need and NevarMail delivers to all of them from a single upstream event.
All endpoints below use the standard v1 API envelope ({ data, requestId, pagination? } on success) and require the webhooks:read scope for reads and webhooks:write for writes.
Base URL: https://app.nevarmail.com (local dev: http://localhost:3400)
Create an endpoint
POST /api/v1/webhook-endpoints{
"url": "https://your-app.com/webhooks/nevarmail",
"description": "Production event handler",
"events": ["email.delivered", "email.bounced"]
}events accepts any of the event types below, or ["*"] to subscribe to all events. Omitting events (or passing []) also matches every event.
Response (201):
{
"data": {
"id": "...",
"url": "https://your-app.com/webhooks/nevarmail",
"description": "Production event handler",
"events": ["email.delivered", "email.bounced"],
"isActive": true,
"failureCount": 0,
"maxFailures": 5,
"lastSuccessAt": null,
"lastFailureAt": null,
"createdAt": "2026-03-22T12:00:00.000Z",
"updatedAt": "2026-03-22T12:00:00.000Z",
"secret": "whsec_..."
},
"requestId": "..."
}maxFailures defaults to 5. When failureCount reaches it after 5 consecutive failed deliveries, NevarMail automatically disables the endpoint (isActive becomes false) and stops sending to it. Re-enable it with PATCH /api/v1/webhook-endpoints/:id and {"isActive": true}, which also resets failureCount back to 0.
The secret is returned only in this response. Store it immediately — NevarMail cannot show it to you again (rotating it via the endpoint-secret route issues a new one, which also can't be retrieved later).
The target url must be a public HTTPS address; NevarMail rejects private/loopback hosts.
List endpoints
GET /api/v1/webhook-endpoints?page=1&perPage=25Returns the same shape as above, without secret, wrapped in the envelope's pagination field (page, perPage, total).
Get an endpoint
GET /api/v1/webhook-endpoints/:idUpdate an endpoint
PATCH /api/v1/webhook-endpoints/:id{
"events": ["email.delivered", "email.bounced", "email.dropped"],
"isActive": true
}Any of url, description, events, isActive may be included; omitted fields are left unchanged.
Delete an endpoint
DELETE /api/v1/webhook-endpoints/:idEvent types
Event delivery is not live yet
You can create endpoints, subscribe to any event below, and send yourself a
test event today — but NevarMail does not yet dispatch these events from real
activity. An endpoint subscribed to email.bounced will not receive anything
when a message bounces. Use analytics and the suppression
list for delivery data until event dispatch ships. The endpoint management,
signing and retry behavior documented on this page are live and will not
change when it does.
The events you can subscribe to:
| Event | Description |
|---|---|
email.sent | Email was accepted by the provider |
email.delivered | Email was delivered to the recipient |
email.deferred | Delivery was temporarily delayed by the receiving server |
email.dropped | The provider dropped the email before attempting delivery |
email.bounced | Email bounced |
email.opened | Recipient opened the email |
email.clicked | Recipient clicked a link |
email.complained | Recipient marked the email as spam |
email.unsubscribed | Recipient unsubscribed |
contact.suppressed | A recipient was added to your suppression list |
domain.verified | A sending domain passed verification |
domain.failed | A sending domain failed verification |
Payload format
The HTTP body NevarMail POSTs to your endpoint is the raw JSON payload for the event — there is no extra { event, timestamp, data } wrapper around it.
Today the only event NevarMail actually dispatches is triggered manually, from an endpoint's Send test event action in the dashboard (POST /api/webhook-endpoints/:id/test), which sends email.sent with this body:
{
"test": true,
"endpoint_id": "550e8400-e29b-41d4-a716-446655440000",
"message": "This is a test webhook event from NevarMail",
"timestamp": "2026-03-22T12:00:03.000Z"
}The other event types listed above are valid subscription values, but nothing in NevarMail's send, tracking, suppression, or domain-verification paths dispatches them yet — subscribing an endpoint to them will not currently produce any deliveries. This page will be updated once real event dispatch ships for those types.
Verifying signatures
Every delivery is signed with HMAC-SHA256, following the Standard Webhooks (Svix) convention. Each request carries:
| Header | Description |
|---|---|
X-Webhook-Id | Unique ID for this delivery attempt |
X-Webhook-Timestamp | Unix timestamp (seconds) the request was signed |
X-Webhook-Signature | v1,<signature> — a base64-encoded HMAC-SHA256 digest |
The signature is computed over the exact string {id}.{timestamp}.{body}, where {id} is the value of X-Webhook-Id, {timestamp} is the value of X-Webhook-Timestamp, and {body} is the raw, unparsed request body — using your endpoint's secret (the whsec_... prefix is stripped before signing) as the HMAC key.
To verify a request:
- Read the raw request body before any JSON parsing — signature verification must run against the exact bytes NevarMail sent.
- Reject the request if
X-Webhook-Timestampis more than 5 minutes old or from the future, to guard against replay. - Recompute the signature and compare it to
X-Webhook-Signatureusing a constant-time comparison.
const crypto = require("crypto");
function verifyNevarMailWebhook(webhookId, timestamp, rawBody, secret, signatureHeader) {
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - Number(timestamp)) > 300) {
throw new Error("Timestamp outside tolerance");
}
const rawSecret = secret.startsWith("whsec_") ? secret.slice(6) : secret;
const signedContent = `${webhookId}.${timestamp}.${rawBody}`;
const expected =
"v1," +
crypto.createHmac("sha256", rawSecret).update(signedContent).digest("base64");
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
throw new Error("Invalid signature");
}
}
// Express example (requires the raw body, not the parsed one):
app.post(
"/webhooks/nevarmail",
express.raw({ type: "application/json" }),
(req, res) => {
verifyNevarMailWebhook(
req.header("X-Webhook-Id"),
req.header("X-Webhook-Timestamp"),
req.body, // Buffer — do not JSON.parse before verifying
process.env.NEVARMAIL_WEBHOOK_SECRET,
req.header("X-Webhook-Signature"),
);
res.sendStatus(200);
},
);Retries and delivery history
Failed deliveries are tracked per endpoint (failureCount / lastFailureAt on the endpoint). A failed delivery (a non-2xx response, timeout, or network error) is retried up to 5 times with exponential backoff, starting at roughly a 10-second delay and increasing with jitter between attempts — 6 total attempts before a delivery is marked failed for good. As described above, 5 consecutive failed deliveries disable the endpoint automatically.
View delivery history, including response codes and errors, for an endpoint from its detail page in the dashboard. There is currently no /api/v1 endpoint for delivery history — it's only available in the dashboard UI, not via API key.
Need webhooks now?
While you build out your webhook handler, you can:
- Poll the analytics API -- With an API key,
GET /api/v1/analytics/summarygives org-level counts andGET /api/v1/analytics/messages/:messageIdgives one message's event timeline. The olderGET /api/analytics/stats/:emailIdis browser-session only — an API key gets403 SESSION_ONLY. - Use inbound routing -- Configure inbound routing rules to forward emails to webhook URLs
- Track events manually --
POST /api/analytics/trackrecords events from your own provider webhook handler; it is also session-only, so it suits an internal tool, not a server-to-server integration