Agent skill

Buttondown Webhooks Skill

Receive and verify Buttondown webhooks. Use when setting up Buttondown newsletter webhook handlers, debugging X-Buttondown-Signature verification, or handling events such as subscriber.created, subscriber.confirmed, subscriber.unsubscribed, email.sent, and automation.invoked.

Install this skill

npx skills add hookdeck/webhook-skills --skill buttondown-webhooks


When to Use This Skill

  • How do I receive Buttondown webhooks?
  • How do I verify the X-Buttondown-Signature header?
  • How do I handle subscriber.created, subscriber.confirmed, subscriber.unsubscribed or email.sent events?
  • Why is my Buttondown webhook signature verification failing, or why are deliveries unsigned?

Set a signing key on the Buttondown webhook. Without one, deliveries are unsigned and fail verification.

Essential Code (USE THIS)

When the webhook has a signing key, every delivery carries X-Buttondown-Signature: sha256=<hex>. The hex is the HMAC-SHA256 of the raw request body bytes, keyed with the signing key as UTF-8 (used as-is, not hex- or base64-decoded). Nothing else is signed: no timestamp, no id, so there is no replay window to check.

Verification (Node.js)

const crypto = require('crypto');

// rawBody: Buffer of the exact request bytes (express.raw({ type: '*/*' }), not express.json)
function verifyButtondown(rawBody, header, secret) {
  if (!secret || !header) return false;
  const sig = header.trim().replace(/^sha256=/i, '');   // prefix optional
  // 64 hex chars first: Buffer.from(x, 'hex') silently truncates invalid hex
  if (!/^[0-9a-f]{64}$/i.test(sig)) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
  return crypto.timingSafeEqual(Buffer.from(sig, 'hex'), expected);
}

Verification (Python)

import hashlib, hmac, re

def verify_buttondown(raw_body: bytes, header: str, secret: str) -> bool:
    if not secret or not header:
        return False
    sig = re.sub(r"^sha256=", "", header.strip(), flags=re.I)   # prefix optional
    if not re.fullmatch(r"[0-9a-fA-F]{64}", sig, re.ASCII):
        return False
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest().encode()
    # bytes on both sides: a non-ASCII header gives a rejection, not a 500
    return hmac.compare_digest(sig.lower().encode(), expected)
  • Missing or empty header, malformed value or mismatch: respond 401. Missing server-side secret: 500.
  • Read the body raw (express.raw({ type: '*/*' }), await request.arrayBuffer(), await request.body()) and verify before parsing JSON.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Setup with Hookdeck

In Hookdeck, create a source of the Buttondown type and enter the same signing key. Set a signing key on the Buttondown webhook. Without one, deliveries are unsigned and fail verification. Use the source URL as the webhook URL in Buttondown. See references/setup.md.

Event Payload and Types

Every payload is { "event_type": "...", "data": { ... } }; data holds ids (for example newsletter and subscriber). Dispatch on event_type. Delivery is at least once and unordered, so make handlers idempotent.

EventTriggered When
subscriber.createdA new subscriber is created
subscriber.confirmedA subscriber confirmed their enrollment (double opt-in)
subscriber.unsubscribedA subscriber manually unsubscribed
email.sentAn email finished its delivery
automation.invokedAn automation is manually invoked (for example by a webhook or API call)

Buttondown documents about 84 event types (subscriber, email, stripe., shopify., patreon., date. and more). The examples log the rest as unhandled. See references/overview.md.

Important Headers

HeaderDescription
X-Buttondown-Signaturesha256=<hex HMAC-SHA256 of the raw body>; sent only when a signing key is set

Environment Variables

BUTTONDOWN_WEBHOOK_SECRET=your_signing_key   # the "Signing key" you set on the webhook, used as-is

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 buttondown --path /webhooks/buttondown

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 11, 2026

View on GitHub →