Webhooks

Receive real-time notifications when email events occur.

Create a Webhook

Create a webhook endpoint to receive event notifications.

POST /v1/webhooks
create-webhook.ts
const webhook = await client.webhooks.create({
  url: 'https://example.com/webhooks/veilmail',
  events: [
    'email.delivered',
    'email.bounced',
    'email.complained',
    'subscriber.unsubscribed',
  ],
  description: 'Production webhook',
});

// Save the secret for signature verification
console.log(webhook.secret); // whsec_xxxxx

Event Types

Subscribe to the following event types:

EventDescription
email.sentEmail was sent
email.deliveredEmail was delivered
email.openedEmail was opened
email.clickedLink in email was clicked
email.bouncedEmail bounced
email.complainedRecipient marked as spam
email.failedEmail failed to send
subscriber.addedSubscriber was added
subscriber.removedSubscriber was removed
subscriber.unsubscribedSubscriber unsubscribed
campaign.sentCampaign started sending
campaign.completedCampaign finished sending

Webhook Payload

Webhook payloads are JSON objects with the following structure:

{
  "id": "evt_xxxxx",
  "type": "email.delivered",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "emailId": "email_xxxxx",
    "to": "user@example.com",
    "subject": "Welcome!",
    "deliveredAt": "2024-01-15T10:30:00Z"
  }
}

Signature Verification

Verify webhook signatures to ensure requests are from Veil Mail. The signature is included in the X-VeilMail-Signature header.

verify-webhook.ts
import crypto from 'crypto';

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Express.js example
app.post('/webhooks/veilmail', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-veilmail-signature'];
  const payload = req.body.toString();

  if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(payload);

  switch (event.type) {
    case 'email.delivered':
      console.log('Email delivered:', event.data.emailId);
      break;
    case 'email.bounced':
      console.log('Email bounced:', event.data.emailId);
      break;
  }

  res.status(200).send('OK');
});

Retry Behavior

Webhook deliveries are retried with exponential backoff if your endpoint returns an error or is unreachable.

  • Retries: Up to 5 attempts
  • Backoff: 1 minute, 5 minutes, 30 minutes, 2 hours, 24 hours
  • Success: HTTP 2xx response within 30 seconds

Test a Webhook

Send a test event to verify your webhook endpoint is working correctly.

POST /v1/webhooks/:id/test
test-webhook.ts
const result = await client.webhooks.test('webhook_xxxxx');

if (result.success) {
  console.log(`Webhook responded in ${result.responseTime}ms`);
} else {
  console.log('Webhook test failed:', result.error);
}

Rotate Signing Secret

Rotate your webhook signing secret for security.

POST /v1/webhooks/:id/rotate-secret
rotate-secret.ts
const webhook = await client.webhooks.rotateSecret('webhook_xxxxx');

// Update your application with the new secret
console.log(webhook.secret); // whsec_new_xxxxx