Skooh.io Logo
Back to blog
IntegrationDeveloper TutorialJuly 16, 20258 min

SendGrid Event Webhooks to a Private Endpoint Using Skooh

Map SendGrid email events to an internal deliverability dashboard. Use replay to backfill historical data.

Overview

SendGrid's Event Webhooks notify you about email delivery, opens, clicks, bounces, and spam reports. This data is gold for understanding email deliverability—but your analytics service is internal.

Skooh connects SendGrid to your private deliverability dashboard.

Setup

1. Create Skooh Endpoint

  1. Go to Skooh Hooks
  2. Create endpoint "SendGrid Events"
  3. Copy URL: `https://api.skooh.io/hook/sg-abc123\`

2. Configure SendGrid

In SendGrid:

  1. Go to Settings → Mail Settings → Event Webhook
  2. Enter your Skooh URL
  3. Select events:
    • Processed, Dropped, Delivered
    • Bounce, Deferred
    • Open, Click
    • Spam Report, Unsubscribe

Note: See SendGrid's Event Webhook documentation for current setup instructions.

3. Run the Skooh Proxy

Using the Desktop App:

  1. Download the Skooh Desktop App
  2. Enter your API key and hook token
  3. Set forwarding URL (e.g., `http://localhost:3000/webhooks/sendgrid\`)
  4. Click Connect

Using Docker:

docker run -d \\
  --name skooh-sendgrid \\
  -e SKOOH_TOKEN=sg-abc123 \\
  -e SKOOH_API_KEY=your_api_key \\
  -e FORWARD_URL=http://email-analytics:3000/webhooks/sendgrid \\
  --restart unless-stopped \\
  skooh/proxy

Handling SendGrid Events

SendGrid sends batches of events:

app.post('/webhooks/sendgrid', express.json(), async (req, res) => {
  const events = req.body; // Array of events
  
  for (const event of events) {
    await processEvent(event);
  }
  
  res.status(200).send('OK');
});

async function processEvent(event) {
  const {
    email,
    timestamp,
    event: eventType,
    sg_message_id,
    category,
    reason,
    url
  } = event;
  
  switch (eventType) {
    case 'delivered':
      await recordDelivery(email, sg_message_id, timestamp);
      break;
      
    case 'open':
      await recordOpen(email, sg_message_id, timestamp);
      break;
      
    case 'click':
      await recordClick(email, sg_message_id, url, timestamp);
      break;
      
    case 'bounce':
      await handleBounce(email, reason, timestamp);
      break;
      
    case 'dropped':
      await handleDropped(email, reason, timestamp);
      break;
      
    case 'spamreport':
      await handleSpamReport(email, timestamp);
      await suppressEmail(email); // Important!
      break;
      
    case 'unsubscribe':
      await handleUnsubscribe(email, timestamp);
      break;
  }
}

Building a Deliverability Dashboard

Track key metrics:

// Store events in your analytics DB
async function recordDelivery(email, messageId, timestamp) {
  await db.emailEvents.insert({
    email,
    message_id: messageId,
    event: 'delivered',
    timestamp: new Date(timestamp * 1000)
  });
  
  // Update aggregate stats
  await db.deliveryStats.increment('delivered', 1);
}

// Calculate deliverability rate
async function getDeliverabilityRate(period) {
  const stats = await db.deliveryStats.get(period);
  return {
    deliveryRate: stats.delivered / stats.sent * 100,
    openRate: stats.opened / stats.delivered * 100,
    clickRate: stats.clicked / stats.delivered * 100,
    bounceRate: stats.bounced / stats.sent * 100,
    spamRate: stats.spam_reports / stats.delivered * 100
  };
}

Replay to Backfill

Setting up a new dashboard? Use Skooh's replay to backfill:

  1. Skooh stores 30 days of webhooks
  2. Go to your SendGrid endpoint in Skooh
  3. Find historical events
  4. Click Replay to resend them
  5. Your dashboard populates with historical data

This is also useful after fixing a bug—replay failed events without waiting for new email sends.

Verifying SendGrid Webhooks

SendGrid supports signed webhooks (recommended):

const crypto = require('crypto');

function verifySignature(publicKey, payload, signature, timestamp) {
  const timestampPayload = timestamp + payload;
  const decodedSignature = Buffer.from(signature, 'base64');
  
  const verifier = crypto.createVerify('sha256');
  verifier.update(timestampPayload);
  
  return verifier.verify(publicKey, decodedSignature);
}

app.post('/webhooks/sendgrid', 
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['x-twilio-email-event-webhook-signature'];
    const timestamp = req.headers['x-twilio-email-event-webhook-timestamp'];
    
    if (!verifySignature(PUBLIC_KEY, req.body, signature, timestamp)) {
      return res.status(401).send('Invalid signature');
    }
    
    const events = JSON.parse(req.body);
    // Process events...
  }
);

Handling High Volume

SendGrid can send many events. Process efficiently:

app.post('/webhooks/sendgrid', express.json(), async (req, res) => {
  // Immediately acknowledge receipt
  res.status(200).send('OK');
  
  // Process in background
  const events = req.body;
  await eventQueue.addBulk(events.map(e => ({ data: e })));
});

Set up SendGrid events →

Written by Skooh Team

Share:

Ready to try Skooh?

Start receiving webhooks in your local environment in under 5 minutes.

Skooh | SendGrid Event Webhooks to a Private Endpoint Using Skooh