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-Signatureheader? - How do I handle
subscriber.created,subscriber.confirmed,subscriber.unsubscribedoremail.sentevents? - 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_; data holds ids (for example newsletter and subscriber). Dispatch on event_type. Delivery is at least once and unordered, so make handlers idempotent.
| Event | Triggered When |
|---|---|
subscriber.created | A new subscriber is created |
subscriber.confirmed | A subscriber confirmed their enrollment (double opt-in) |
subscriber.unsubscribed | A subscriber manually unsubscribed |
email.sent | An email finished its delivery |
automation.invoked | An 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
| Header | Description |
|---|---|
X-Buttondown-Signature | sha256=<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
- references/overview.md - Buttondown webhook concepts, events, payloads, retries
- references/setup.md - Creating the webhook, setting the signing key, Hookdeck setup
- references/verification.md - Signature details, gotchas, and debugging