WWistfare Mail

Webhooks

Receive real-time notifications for email delivery events via HTTP webhooks.

Events

Wistfare Mail can send webhook notifications for the following events:

EventDescription
email.sentEmail accepted and sent to recipient server
email.deliveredEmail confirmed delivered to inbox
email.bouncedEmail delivery failed (hard or soft bounce)
email.openedRecipient opened the email
email.clickedRecipient clicked a link in the email
email.complainedRecipient marked the email as spam
email.failedEmail sending failed due to an error
email.receivedIncoming email received on a verified domain

Create a Webhook

POST /api/v1/webhooks

Scope: webhooks:manage

Request Body

FieldTypeRequiredDescription
urlstringYesHTTPS endpoint to receive events
eventsstring[]YesList of events to subscribe to
const webhook = await wm.webhooks.create({
  url: 'https://yourapp.com/webhooks/wistmail',
  events: ['email.delivered', 'email.bounced', 'email.opened'],
})
 
console.log(webhook.id) // whk_abc123
console.log(webhook.signingSecret) // whsec_... (shown once)

Response

{
  "id": "whk_abc123",
  "url": "https://yourapp.com/webhooks/wistmail",
  "events": ["email.delivered", "email.bounced", "email.opened"],
  "signingSecret": "whsec_a1b2c3d4e5f6...",
  "active": true,
  "createdAt": "2026-03-28T00:00:00Z"
}

The signingSecret is only returned at creation time. Store it securely for signature verification.


List Webhooks

GET /api/v1/webhooks

Scope: webhooks:manage

const webhooks = await wm.webhooks.list()
webhooks.forEach(w => console.log(w.url, w.events))

Update a Webhook

PATCH /api/v1/webhooks/:id

Scope: webhooks:manage

Request Body

FieldTypeRequiredDescription
urlstringNoUpdated endpoint URL
eventsstring[]NoUpdated event subscriptions
activebooleanNoEnable or disable the webhook
const updated = await wm.webhooks.update('whk_abc123', {
  events: ['email.delivered', 'email.bounced', 'email.opened', 'email.clicked'],
})

Delete a Webhook

DELETE /api/v1/webhooks/:id

Scope: webhooks:manage

await wm.webhooks.delete('whk_abc123')

Returns 204 No Content on success.


Test a Webhook

POST /api/v1/webhooks/:id/test

Scope: webhooks:manage

Sends a test payload to your endpoint and returns the HTTP status code received.

const result = await wm.webhooks.test('whk_abc123')
console.log(result.status) // 200

Response

{
  "status": 200
}

Webhook Payload

When an event fires, Wistfare Mail sends a POST request to your endpoint with this payload:

{
  "id": "evt_abc123",
  "type": "email.delivered",
  "createdAt": "2026-03-28T12:00:00Z",
  "data": {
    "emailId": "eml_abc123",
    "from": "hello@wistfare.com",
    "to": "user@example.com",
    "subject": "Welcome!",
    "deliveredAt": "2026-03-28T12:00:01Z"
  }
}

Your endpoint must return a 2xx status code within 10 seconds. Failed deliveries are retried with exponential backoff up to 3 times.


Signature Verification

Every webhook request includes a Wistmail-Signature header for verifying authenticity. The signature is an HMAC-SHA256 hex digest of the raw request body using your signingSecret.

import crypto from 'node:crypto'
 
function verifyWebhook(body: string, signature: string, secret: string): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body)
    .digest('hex')
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  )
}
 
// In your request handler:
const body = await request.text()
const signature = request.headers.get('Wistmail-Signature')
const isValid = verifyWebhook(body, signature, 'whsec_your_signing_secret')
 
if (!isValid) {
  return new Response('Invalid signature', { status: 401 })
}
 
const event = JSON.parse(body)
// Handle event...

On this page