Help us improve
Share bugs, ideas, or general feedback.
From godmode
Guides webhook design, inbound handling with HMAC verification and idempotency, outbound delivery with retries, circuit breakers, and dead letter queues.
npx claudepluginhub arbazkhan971/godmodeHow this skill is triggered — by the user, by Claude, or both
Slash command
/godmode:webhookThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
- `/godmode:webhook`, "write a webhook", "webhook handler"
Creates secure webhook handlers with HMAC signature verification, idempotency, retry logic, and dead letter queues. Activates on webhook-related requests.
Designs reliable webhook systems for APIs covering registration, payload schema/versioning, at-least-once delivery, retries, idempotency, and fan-out. Use for new platforms, audits, or developer docs.
Creates webhook endpoints with HMAC signature verification, idempotency checks, payload parsing, and async retry handling for Stripe, GitHub, Twilio.
Share bugs, ideas, or general feedback.
/godmode:webhook, "write a webhook", "webhook handler"# Detect webhook libraries and patterns
grep -r "svix\|standardwebhooks\|hmac\|createHmac" \
package.json src/ --include="*.ts" -l 2>/dev/null
grep -r "webhook" --include="*.sql" \
--include="*.prisma" -l 2>/dev/null
# Check queue integration
grep -r "BullMQ\|bull\|celery\|SQS\|amqplib" \
package.json 2>/dev/null
WEBHOOK DISCOVERY:
Direction: Inbound | Outbound | Both
Events: <list event types>
Volume: <events/sec, daily total>
Latency SLA: <max delay>
Delivery: At-least-once | Best-effort
Auth: HMAC-SHA256 | mTLS | bearer token
IF inbound only: skip delivery system (Step 4)
IF outbound only: skip inbound handler (Step 3)
IF volume > 1000/sec: add partitioned queue
IF volume < 10/min: simple async dispatch OK
NAMING: <resource>.<past_tense_verb>
e.g., order.created, payment.completed
ENVELOPE:
id: evt_<ULID> (unique, sortable)
type: resource.verb
api_version: 2025-01-15
created_at: ISO 8601 UTC
data: { object: {...}, previous_attributes: {} }
livemode: true|false
RULES:
Max payload: 64KB
Monetary values: cents (integer)
Timestamps: ISO 8601 UTC
NEVER include PII or secrets in payloads
IF payload > 64KB: send reference + fetch URL
WHEN previous state needed: include diff
HANDLER PATTERN (strict order):
1. Verify HMAC-SHA256 signature
Use raw bytes, constant-time comparison
IF invalid: return 401 immediately
2. Check timestamp (5 min tolerance window)
IF stale (>300s drift): return 403
3. Check idempotency (event ID seen?)
IF duplicate: return 200 (already processed)
4. Parse payload, route to handler
5. Process in transaction
6. Return 200 within 5s timeout
IF heavy work: offload to queue
SSRF PREVENTION (inbound callback URLs):
Resolve DNS before connecting
Block: 127.0.0.0/8, 10.0.0.0/8,
172.16.0.0/12, 192.168.0.0/16,
169.254.0.0/16, ::1
Re-check on every retry
PIPELINE:
Event Source -> Dispatcher -> Worker -> Endpoint
Delivery Log Retry Queue (exp backoff)
|
Dead Letter Queue
RETRY: 10 attempts over ~3 days
Delays: 0s, 10s, 30s, 1m, 5m, 15m, 1h, 4h, 8h, 24h
Jitter: random 0.5x-1.5x multiplier
IF 5xx, 408, 429, timeout, DNS fail: retry
IF 4xx (except 408/429): do NOT retry
WHEN Retry-After header present: honor it
CIRCUIT BREAKER (per endpoint):
CLOSED -> 5 consecutive failures -> OPEN
OPEN: queue events, alert owner
After 30 min -> HALF-OPEN (test 1 event)
IF 2 successes -> CLOSED
IF failure -> OPEN again
Auto-disable after 72 hours open
Events land here after max retries (10)
or prolonged circuit break (>72h)
Retention: 30 days
API: replay single, replay bulk, export, purge
WHEN DLQ grows > 1000 entries: alert + analyze
POST/GET /api/v1/webhooks Create/list
GET/PATCH /api/v1/webhooks/:id Get/update
DELETE /api/v1/webhooks/:id Delete
POST /api/v1/webhooks/:id/test Test ping
POST /api/v1/webhooks/:id/rotate Rotate secret
GET /api/v1/webhooks/:id/deliveries History
LIMITS:
HTTPS only (no HTTP in production)
Public IP only (SSRF block)
Max 100 event types per subscription
Max 20 subscriptions per account
Event filtering: ["*"], ["order.*"], specific
WHEN subscription created: send test ping
IF test ping fails: warn but allow save
Dual-secret zero-downtime rotation:
Phase 1: sign with both old + new secret
Phase 2: grace period 24-72 hours
Phase 3: revoke old secret
Secret shown ONCE at creation.
Rotation cadence: 90 days recommended.
IF >180 days since rotation: warn in dashboard
-- Core tables
webhook_subscriptions (
id, account_id, url, events JSONB,
secret_current, secret_previous,
circuit_state, failure_count,
disabled_at, created_at, updated_at
)
webhook_events (id, type, data JSONB, created_at)
webhook_deliveries (
id, event_id, subscription_id,
status, attempts, next_retry_at,
response_code, response_body_preview,
created_at
)
webhook_dead_letter (
delivery_id, reason, replayed_at
)
Indexes: subscription(account_id),
delivery(event_id, subscription_id),
delivery(status, next_retry_at)
METRICS (alert thresholds):
Delivery success rate: target >99%
IF <95%: P1 alert
First-attempt success: target >95%
IF <90%: investigate endpoint health
P50 delivery latency: <500ms
P99 delivery latency: <5s
DLQ entries: alert if >100 pending
Open circuit breakers: alert if >0
Retry queue depth: alert if >10000
Commit: "webhook: <desc> -- <components>"
grep -r "svix\|standardwebhooks" package.json \
2>/dev/null
grep -r "createHmac\|timingSafeEqual" \
src/ --include="*.ts" -l 2>/dev/null
grep -r "webhook" migrations/ --include="*.sql" \
-l 2>/dev/null
Log to .godmode/webhook-results.tsv:
timestamp\tproject\tdirection\tevent_types\tretry\tdlq\tssrf\tcommit
Webhook: {endpoints} endpoints, {issues} issues
-> {fixed} fixed. Status: {DONE|PARTIAL}.
KEEP if: delivery succeeds AND retry works
AND idempotency verified AND HMAC validates
DISCARD if: delivery fails after 10 retries
OR duplicate processing detected
Revert: git reset --hard HEAD~1
STOP when FIRST of:
- All endpoints verified (retry + idempotency)
- Max iterations reached (budget exhausted)
- 3 consecutive iterations <1% improvement
- >5 consecutive discards