Webhook Integration Guide

Learn how to integrate webhooks into your application to receive real-time notifications about email events and other actions.

Overview

Webhooks allow your application to receive real-time notifications when events occur in Metigan. Instead of polling for updates, webhooks push data to your server as soon as events happen.

1. Create Webhook

Set up a webhook endpoint in your Metigan dashboard or via API

2. Receive Events

Your endpoint receives POST requests with event data

3. Verify & Process

Verify the signature and process the event in your app

Quick Start

Here's a minimal example to get started with webhooks in Node.js:

webhook-server.tsTypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import express from 'express';
import crypto from 'crypto';

const app = express();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!;

// IMPORTANT: Use express.raw() to preserve the raw body for signature verification
app.post('/webhooks/metigan', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'] as string;
  const payload = req.body.toString();
  
  // Verify the signature
  if (!verifySignature(payload, signature, WEBHOOK_SECRET)) {
    console.error('Invalid webhook signature');
    return res.status(401).send('Invalid signature');
  }
  
  // Parse and process the event
  const event = JSON.parse(payload);
  
  switch (event.event) {
    case 'email.delivered':
      console.log(`Email delivered to ${event.data.recipient}`);
      break;
    case 'email.opened':
      console.log(`Email opened by ${event.data.recipient}`);
      break;
    case 'email.clicked':
      console.log(`Link clicked in email to ${event.data.recipient}`);
      break;
    case 'email.bounced':
      console.log(`Email bounced for ${event.data.recipient}`);
      break;
    default:
      console.log(`Received event: ${event.event}`);
  }
  
  res.status(200).send('OK');
});

function verifySignature(payload: string, signature: string, secret: string): boolean {
  try {
    const parts = signature.split(',');
    const timestamp = parseInt(parts.find(p => p.startsWith('t='))!.slice(2));
    const providedSig = parts.find(p => p.startsWith('v1='))!.slice(3);
    
    // Check timestamp (5 minute tolerance)
    if (Math.floor(Date.now() / 1000) - timestamp > 300) return false;
    
    // Verify HMAC
    const expectedSig = crypto
      .createHmac('sha256', secret)
      .update(`${timestamp}.${payload}`)
      .digest('hex');
    
    return crypto.timingSafeEqual(Buffer.from(providedSig), Buffer.from(expectedSig));
  } catch {
    return false;
  }
}

app.listen(3000, () => console.log('Webhook server running on port 3000'));
Signature Verification

The signature uses HMAC-SHA256 with the format t=timestamp,v1=signature. The signed payload is timestamp.payload (timestamp dot raw JSON body).

Common Use Cases

📊 Track Email Engagement

Use email.opened and email.clicked events to track user engagement and update your analytics.

track-engagement.tsTypeScript
1
2
3
4
5
6
7
8
9
// Update user engagement score when they interact with emails
switch (event.event) {
  case 'email.opened':
    await db.users.updateEngagement(event.data.userId, { opens: 1 });
    break;
  case 'email.clicked':
    await db.users.updateEngagement(event.data.userId, { clicks: 1 });
    break;
}

🚫 Handle Bounces

Automatically clean your mailing list when emails bounce.

handle-bounces.tsTypeScript
1
2
3
4
5
6
7
8
9
10
11
if (event.event === 'email.bounced') {
  const { recipient, bounceType } = event.data;
  
  if (bounceType === 'hard') {
    // Permanently invalid address - remove from list
    await db.contacts.markInvalid(recipient);
  } else {
    // Soft bounce - retry later
    await db.contacts.incrementBounceCount(recipient);
  }
}

⚠️ Monitor Spam Complaints

Handle spam complaints to maintain sender reputation.

handle-complaints.tsTypeScript
1
2
3
4
5
6
7
8
9
if (event.event === 'email.complained') {
  const { recipient } = event.data;
  
  // Unsubscribe user immediately
  await db.contacts.unsubscribe(recipient, 'spam_complaint');
  
  // Alert your team
  await slack.notify(`⚠️ Spam complaint from ${recipient}`);
}

Best Practices

Always verify signatures

Never process webhook data without verifying the HMAC signature first.

Respond quickly

Return a 2xx response within 30 seconds. For long operations, queue the work and respond immediately.

🔒

Use HTTPS only

Webhook endpoints must use HTTPS to ensure data is encrypted in transit.

🔄

Handle idempotency

Use the messageId to ensure you don't process the same event twice if retries occur.

Security

Always verify webhook signatures to ensure requests are authentic. Never process webhook payloads without verification. See the Webhooks API documentation for complete code examples.

Testing Webhooks

You can test your webhook integration directly from the Metigan dashboard:

  1. Go to Settings → Webhooks in your dashboard
  2. Select the webhook you want to test
  3. Click the "Test Webhook" button
  4. Choose an event type and send a test payload
  5. Check the delivery history to see the result

For local development, you can use tools like ngrok or localtunnel to expose your local server to the internet.