Claude
Skills
Sign in
Back

webhook-handlers

Included with Lifetime
$97 forever

Webhook event handling patterns for email tracking (sent, delivered, bounced, opened, clicked). Use when implementing email event webhooks, signature verification, processing delivery events, logging email analytics, or building real-time email status tracking.

Productivityscripts

What this skill does


# Webhook Handlers Skill

Comprehensive patterns and templates for implementing secure webhook handlers with Resend, covering event types, signature verification, and event processing strategies.

## Use When

- Implementing webhook endpoints for email events (sent, delivered, bounced, opened, clicked)
- Setting up signature verification for webhook authenticity
- Building email tracking and analytics systems
- Processing bounce and complaint events for list management
- Creating real-time email status dashboards
- Logging delivery events to database
- Implementing retry logic for webhook processing
- Handling multiple webhook events in parallel

## Webhook Event Types

### Resend Webhook Events

Resend sends webhooks for the following email events:

#### 1. Email Sent

Triggered when email is accepted by Resend.

```json
{
  "type": "email.sent",
  "created_at": "2024-01-15T10:30:00Z",
  "data": {
    "email_id": "123e4567-e89b-12d3-a456-426614174000",
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Welcome to Example",
    "created_at": "2024-01-15T10:30:00Z"
  }
}
```

#### 2. Email Delivered

Triggered when email reaches recipient's mail server.

```json
{
  "type": "email.delivered",
  "created_at": "2024-01-15T10:35:00Z",
  "data": {
    "email_id": "123e4567-e89b-12d3-a456-426614174000",
    "from": "[email protected]",
    "to": "[email protected]",
    "created_at": "2024-01-15T10:35:00Z"
  }
}
```

#### 3. Email Bounced

Triggered when email cannot be delivered (hard bounce).

```json
{
  "type": "email.bounced",
  "created_at": "2024-01-15T10:40:00Z",
  "data": {
    "email_id": "123e4567-e89b-12d3-a456-426614174000",
    "from": "[email protected]",
    "to": "[email protected]",
    "reason": "Mailbox does not exist",
    "created_at": "2024-01-15T10:40:00Z"
  }
}
```

#### 4. Email Opened

Triggered when recipient opens the email (requires pixel tracking).

```json
{
  "type": "email.opened",
  "created_at": "2024-01-15T11:00:00Z",
  "data": {
    "email_id": "123e4567-e89b-12d3-a456-426614174000",
    "from": "[email protected]",
    "to": "[email protected]",
    "user_agent": "Mozilla/5.0...",
    "ip_address": "192.168.1.1",
    "created_at": "2024-01-15T11:00:00Z"
  }
}
```

#### 5. Email Clicked

Triggered when recipient clicks a link in the email.

```json
{
  "type": "email.clicked",
  "created_at": "2024-01-15T11:05:00Z",
  "data": {
    "email_id": "123e4567-e89b-12d3-a456-426614174000",
    "from": "[email protected]",
    "to": "[email protected]",
    "link": "https://example.com/promo",
    "user_agent": "Mozilla/5.0...",
    "ip_address": "192.168.1.1",
    "created_at": "2024-01-15T11:05:00Z"
  }
}
```

#### 6. Email Complained

Triggered when recipient marks email as spam.

```json
{
  "type": "email.complained",
  "created_at": "2024-01-15T11:10:00Z",
  "data": {
    "email_id": "123e4567-e89b-12d3-a456-426614174000",
    "from": "[email protected]",
    "to": "[email protected]",
    "created_at": "2024-01-15T11:10:00Z"
  }
}
```

## Signature Verification Patterns

### TypeScript Signature Verification

Verify webhook authenticity using HMAC-SHA256:

```typescript
import crypto from 'crypto';

interface WebhookEvent {
  type: string;
  created_at: string;
  data: Record<string, any>;
}

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

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

// Usage in Express middleware
import express from 'express';

const webhookRouter = express.Router();

webhookRouter.post('/webhooks/resend', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-resend-signature'] as string;
  const payload = req.body.toString();

  if (!verifyWebhookSignature(payload, signature, process.env.RESEND_WEBHOOK_SECRET!)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const event: WebhookEvent = JSON.parse(payload);
  handleWebhookEvent(event);

  res.json({ success: true });
});

export default webhookRouter;
```

### Python Signature Verification

```python
import hmac
import hashlib
import json
from typing import Tuple

def verify_webhook_signature(
    payload: str,
    signature: str,
    signing_secret: str
) -> bool:
    """Verify Resend webhook signature using HMAC-SHA256."""
    expected_signature = hmac.new(
        signing_secret.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected_signature)


def get_signature_from_headers(headers: dict) -> str:
    """Extract signature from request headers."""
    return headers.get('x-resend-signature', '')
```

## Event Processing Strategies

### TypeScript Event Handler

```typescript
interface EmailEvent {
  type: 'sent' | 'delivered' | 'bounced' | 'opened' | 'clicked' | 'complained';
  created_at: string;
  data: {
    email_id: string;
    from: string;
    to: string;
    [key: string]: any;
  };
}

async function handleWebhookEvent(event: EmailEvent): Promise<void> {
  try {
    // Log the event
    console.log(`Processing webhook: ${event.type}`, {
      email_id: event.data.email_id,
      timestamp: event.created_at,
    });

    // Route to specific handler
    switch (event.type) {
      case 'email.sent':
        await handleEmailSent(event.data);
        break;

      case 'email.delivered':
        await handleEmailDelivered(event.data);
        break;

      case 'email.bounced':
        await handleEmailBounced(event.data);
        break;

      case 'email.opened':
        await handleEmailOpened(event.data);
        break;

      case 'email.clicked':
        await handleEmailClicked(event.data);
        break;

      case 'email.complained':
        await handleEmailComplained(event.data);
        break;

      default:
        console.warn(`Unknown event type: ${event.type}`);
    }
  } catch (error) {
    console.error('Error handling webhook event:', error);
    throw error;
  }
}

// Individual event handlers
async function handleEmailSent(data: any): Promise<void> {
  // Update email status in database
  await db.emails.update(
    { id: data.email_id },
    {
      status: 'sent',
      sent_at: new Date(data.created_at),
      updated_at: new Date(),
    }
  );
}

async function handleEmailDelivered(data: any): Promise<void> {
  await db.emails.update(
    { id: data.email_id },
    {
      status: 'delivered',
      delivered_at: new Date(data.created_at),
      updated_at: new Date(),
    }
  );
}

async function handleEmailBounced(data: any): Promise<void> {
  // Update status and mark recipient as invalid
  await db.emails.update(
    { id: data.email_id },
    {
      status: 'bounced',
      bounce_reason: data.reason,
      bounced_at: new Date(data.created_at),
      updated_at: new Date(),
    }
  );

  // Add to bounce list
  await db.bounced_emails.create({
    email: data.to,
    reason: data.reason,
    bounced_at: new Date(data.created_at),
  });
}

async function handleEmailOpened(data: any): Promise<void> {
  await db.email_events.create({
    email_id: data.email_id,
    event_type: 'opened',
    user_agent: data.user_agent,
    ip_address: data.ip_address,
    created_at: new Date(data.created_at),
  });
}

async function handleEmailClicked(data: any): Promise<void> {
  await db.email_events.create({
    email_id: data.email_id,
    event_type: 'clicked',
    link: data.link,
    user_agent: data.user_agent,
    ip_address: data.ip_address,
    created_at: new Date(data.created_at),
  });
}

async function handleEmailComplained(data: any): Promise<void> {
  await db.emails.update(
    { id: data.email_id },
    {
      status: 'complained',
   

Related in Productivity