NevarMail

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=25

Returns 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/:id

Update 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/:id

Event 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:

EventDescription
email.sentEmail was accepted by the provider
email.deliveredEmail was delivered to the recipient
email.deferredDelivery was temporarily delayed by the receiving server
email.droppedThe provider dropped the email before attempting delivery
email.bouncedEmail bounced
email.openedRecipient opened the email
email.clickedRecipient clicked a link
email.complainedRecipient marked the email as spam
email.unsubscribedRecipient unsubscribed
contact.suppressedA recipient was added to your suppression list
domain.verifiedA sending domain passed verification
domain.failedA 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:

HeaderDescription
X-Webhook-IdUnique ID for this delivery attempt
X-Webhook-TimestampUnix timestamp (seconds) the request was signed
X-Webhook-Signaturev1,<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:

  1. Read the raw request body before any JSON parsing — signature verification must run against the exact bytes NevarMail sent.
  2. Reject the request if X-Webhook-Timestamp is more than 5 minutes old or from the future, to guard against replay.
  3. Recompute the signature and compare it to X-Webhook-Signature using 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/summary gives org-level counts and GET /api/v1/analytics/messages/:messageId gives one message's event timeline. The older GET /api/analytics/stats/:emailId is browser-session only — an API key gets 403 SESSION_ONLY.
  • Use inbound routing -- Configure inbound routing rules to forward emails to webhook URLs
  • Track events manually -- POST /api/analytics/track records events from your own provider webhook handler; it is also session-only, so it suits an internal tool, not a server-to-server integration

On this page