WWistfare Mail

Verifying Webhook Signatures

Every webhook delivery is signed with HMAC-SHA256 so you can verify it came from Wistfare Mail.

How Signatures Work

Each webhook delivery includes these headers:

HeaderValue
X-Webhook-Signaturesha256=<hex digest>
X-Webhook-EventEvent type (e.g., email.delivered)
X-Webhook-IdUnique event ID (for idempotency)

The signature is computed as HMAC-SHA256(secret, raw_request_body). The signing secret is returned once when you create the webhook — store it securely.

Verification Examples

import crypto from 'node:crypto'
import express from 'express'
 
const app = express()
 
app.post(
  '/webhooks/wistmail',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.header('X-Webhook-Signature') || ''
    const secret = process.env.WISTMAIL_WEBHOOK_SECRET!
 
    const expected = 'sha256=' + crypto
      .createHmac('sha256', secret)
      .update(req.body)
      .digest('hex')
 
    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
      return res.status(401).json({ error: 'invalid signature' })
    }
 
    const event = JSON.parse(req.body.toString())
    console.log(event.type, event.data)
    res.status(200).json({ received: true })
  },
)

Retry Behavior

If your endpoint returns a non-2xx status (or times out after 10 seconds), the delivery is retried up to 3 times with exponential backoff: 1 second, 5 seconds, then 30 seconds.

4xx responses (except 429) are not retried — fix the issue on your side and we'll send the next event normally.

Idempotency

The X-Webhook-Id header uniquely identifies each event. If you process events in a queue, dedupe by this ID to handle retries gracefully.

Testing Locally

Use a tunnel like ngrok or webhook.site to receive events on your dev machine:

ngrok http 3000
# Copy the https URL (e.g., https://abc123.ngrok.io) into
# Settings > Webhooks in the dashboard.

On this page