Skip to content
Back to skills

Webhooks

ASecurity

Webhook patterns for sending and receiving. Signature verification, retry logic, idempotency, payload design, and webhook infrastructure. Covers Stripe, GitHub, Slack webhook consumption and custom webhook dispatch. USE WHEN: user mentions "webhook", "webhook endpoint", "webhook signature", "event callback", "HTTP callback", "webhook retry", "webhook dispatch" DO NOT USE FOR: WebSocket real-time - use `socket-io` or `sse`; message queues - use messaging skills

  • 31 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 8, 2026
ai-agentstypescriptexpressdebugginggitapi

Works with

  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add claude-dev-suite/claude-dev-suite --skill webhooks --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Webhooks?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Webhooks
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/claude-dev-suite-webhooks/badge)](https://www.skillsdirectory.com/skills/claude-dev-suite-webhooks)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: webhooks
description: |
  Webhook patterns for sending and receiving. Signature verification, retry
  logic, idempotency, payload design, and webhook infrastructure. Covers
  Stripe, GitHub, Slack webhook consumption and custom webhook dispatch.

  USE WHEN: user mentions "webhook", "webhook endpoint", "webhook signature",
  "event callback", "HTTP callback", "webhook retry", "webhook dispatch"

  DO NOT USE FOR: WebSocket real-time - use `socket-io` or `sse`;
  message queues - use messaging skills
allowed-tools: Read, Grep, Glob, Write, Edit
---
# Webhooks

## Receiving Webhooks

### Express (with signature verification)

```typescript
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['stripe-signature']!;
  const event = stripe.webhooks.constructEvent(req.body, signature, WEBHOOK_SECRET);

  // Idempotency: check if already processed
  const existing = await db.webhookEvent.findUnique({ where: { eventId: event.id } });
  if (existing) return res.json({ received: true });

  // Process
  switch (event.type) {
    case 'checkout.session.completed':
      await fulfillOrder(event.data.object);
      break;
  }

  // Mark as processed
  await db.webhookEvent.create({ data: { eventId: event.id, type: event.type } });
  res.json({ received: true });
});
```

### Generic HMAC Verification

```typescript
import { createHmac, timingSafeEqual } from 'crypto';

function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {
  const expected = createHmac('sha256', secret).update(payload).digest('hex');
  return timingSafeEqual(Buffer.from(signature), Buffer.from(`sha256=${expected}`));
}

app.post('/webhooks/github', express.raw({ type: '*/*' }), (req, res) => {
  const sig = req.headers['x-hub-signature-256'] as string;
  if (!verifyWebhookSignature(req.body.toString(), sig, GITHUB_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  // Process event...
  res.status(200).json({ received: true });
});
```

## Sending Webhooks

```typescript
class WebhookDispatcher {
  async dispatch(url: string, event: WebhookEvent, secret: string) {
    const payload = JSON.stringify(event);
    const signature = createHmac('sha256', secret).update(payload).digest('hex');

    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Webhook-Signature': `sha256=${signature}`,
        'X-Webhook-Id': event.id,
        'X-Webhook-Timestamp': new Date().toISOString(),
      },
      body: payload,
      signal: AbortSignal.timeout(10000),
    });

    if (!response.ok) throw new WebhookDeliveryError(response.status);
  }
}
```

### Retry with Exponential Backoff

```typescript
// Use job queue for reliable delivery
await webhookQueue.add('deliver', {
  url: subscription.url,
  event: { id: uuid(), type: 'order.created', data: order },
  secret: subscription.secret,
}, {
  attempts: 5,
  backoff: { type: 'exponential', delay: 60000 }, // 1m, 2m, 4m, 8m, 16m
});
```

## Webhook Event Schema

```typescript
interface WebhookEvent {
  id: string;           // Unique event ID (for idempotency)
  type: string;         // 'order.created', 'user.deleted'
  timestamp: string;    // ISO 8601
  data: unknown;        // Event payload
  version: string;      // API version
}
```

## Anti-Patterns

| Anti-Pattern | Fix |
|--------------|-----|
| No signature verification | Always verify HMAC signature |
| Processing before responding 200 | Respond 200 immediately, process async |
| No idempotency check | Store processed event IDs |
| Using parsed body for verification | Use raw body for signature check |
| No retry on send failures | Use job queue with exponential backoff |
| Synchronous webhook delivery | Dispatch via background job queue |

## Production Checklist

- [ ] HMAC signature verification on receive
- [ ] Raw body parsing (not JSON-parsed) for signature
- [ ] Idempotency: deduplicate by event ID
- [ ] Respond 200 before processing
- [ ] Retry with exponential backoff on send
- [ ] Dead letter queue for failed deliveries
- [ ] Webhook event log for debugging

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…