The interface is localized. Original skills retain their author's language; some long-form pages are provided in English.
Explore skills
Design & creative · SKILL.md

webhooks

webhooks

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 dispa…

Independent GitHub snapshot · Automated classification · Original documentation governs

Skill documentation

GitHub

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)

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

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

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

// 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

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-PatternFix
No signature verificationAlways verify HMAC signature
Processing before responding 200Respond 200 immediately, process async
No idempotency checkStore processed event IDs
Using parsed body for verificationUse raw body for signature check
No retry on send failuresUse job queue with exponential backoff
Synchronous webhook deliveryDispatch 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
Saved skills