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:

Email Events

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
email.delivery_delayedEmail delivery was delayed by the receiving server

Subscriber Events

EventDescription
subscriber.addedSubscriber was added
subscriber.removedSubscriber was removed
subscriber.unsubscribedSubscriber unsubscribed

Contact Events

EventDescription
contact.createdA new contact was added to an audience
contact.updatedA contact's details or status were updated
contact.deletedA contact was removed from an audience

Domain Events

EventDescription
domain.createdA new sending domain was added
domain.verifiedA domain completed DNS verification
domain.deletedA sending domain was removed

Campaign Events

EventDescription
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"
  }
}

Contact Event Payloads

Contact events fire when subscribers are created, updated, or deleted in your audiences.

contact.created

{
  "id": "evt_xxxxx",
  "type": "contact.created",
  "created": "2024-01-15T10:30:00Z",
  "data": {
    "subscriberId": "sub_xxxxx",
    "audienceId": "aud_xxxxx",
    "email": "user@example.com",
    "status": "active"
  }
}

contact.updated

{
  "id": "evt_xxxxx",
  "type": "contact.updated",
  "created": "2024-01-15T10:30:00Z",
  "data": {
    "subscriberId": "sub_xxxxx",
    "audienceId": "aud_xxxxx",
    "email": "user@example.com",
    "changes": ["firstName", "lastName", "status"]
  }
}

contact.deleted

{
  "id": "evt_xxxxx",
  "type": "contact.deleted",
  "created": "2024-01-15T10:30:00Z",
  "data": {
    "subscriberId": "sub_xxxxx",
    "audienceId": "aud_xxxxx"
  }
}

Domain Event Payloads

Domain events fire when sending domains are added, verified, or removed.

domain.created

{
  "id": "evt_xxxxx",
  "type": "domain.created",
  "created": "2024-01-15T10:30:00Z",
  "data": {
    "domainId": "dom_xxxxx",
    "domain": "mail.example.com",
    "status": "pending"
  }
}

domain.verified

{
  "id": "evt_xxxxx",
  "type": "domain.verified",
  "created": "2024-01-15T10:30:00Z",
  "data": {
    "domainId": "dom_xxxxx",
    "domain": "mail.example.com"
  }
}

domain.deleted

{
  "id": "evt_xxxxx",
  "type": "domain.deleted",
  "created": "2024-01-15T10:30:00Z",
  "data": {
    "domainId": "dom_xxxxx",
    "domain": "mail.example.com"
  }
}

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