spb/ultra-sharp-agent-skills Public
Ultra-Sharp Agent Skills — a research-first skill-authoring system + 72 production-ready skills for AI agents.
Python 100%
1<!--2Author: Simon-Pierre Boucher3Contact: contact@spboucher.ai4-->56# Patterns — Designing Webhooks78## Contents9- Delivery envelope10- Signing (producer) and verification (consumer)11- Retry schedule12- Consumer receiver checklist13- Dead-letter and redelivery API14- Gotchas1516## Delivery envelope1718```json19{20 "delivery_id": "whd_01J8ZC4T9",21 "type": "invoice.paid",22 "created_at": "2026-08-05T14:30:00Z",23 "api_version": "2026-06-01",24 "data": { "invoice_id": "inv_42" }25}26```27`delivery_id` is unique per event and STABLE across retries — it is the28consumer's dedup key. `api_version` pins the payload shape per subscriber.2930## Signing (producer) and verification (consumer)3132Producer:3334```python35import hmac, hashlib, time3637def sign(secret: str, body: bytes) -> str:38 ts = str(int(time.time()))39 msg = ts.encode() + b"." + body40 sig = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()41 return f"t={ts},v1={sig}"42# Header: Webhook-Signature: t=1754404200,v1=5257a86...43```4445Consumer:4647```python48# 300 s = 5 min replay window: generous for clock skew, tight for replays.49MAX_SKEW = 3005051def verify(secret, header, body) -> bool:52 parts = dict(p.split("=", 1) for p in header.split(","))53 ts, their_sig = parts["t"], parts["v1"]54 if abs(time.time() - int(ts)) > MAX_SKEW:55 return False56 msg = ts.encode() + b"." + body57 ours = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()58 return hmac.compare_digest(ours, their_sig) # constant time59```6061Verify against the RAW request body bytes — any re-serialization (JSON parse62then dump) changes the bytes and fails the check.6364## Retry schedule6566```67attempt delay cumulative681 immediate 0692 1 min 1 min703 5 min 6 min714 30 min 36 min725 2 h ~2.6 h736 12 h ~14.6 h → dead-letter after this74```75Add ±20% jitter to every delay so a consumer recovering from an outage is not76hit by a synchronized thundering herd. Timeout per attempt: 10 s.7778## Consumer receiver checklist (publish this in your docs)7980```811. Verify Webhook-Signature before parsing (raw body, constant-time compare).822. Reject t older than 5 minutes.833. Return 200 immediately; process async (queue). Slow handlers get retried84 and you will process duplicates.854. Deduplicate on delivery_id (store processed IDs ≥ 24 h).865. Treat events as unordered; fetch current state from the API when it matters.876. Do not whitelist our IPs as your only security — verify signatures.88```8990## Dead-letter and redelivery API9192```93GET /v1/webhook-deliveries?status=failed&endpoint_id=we_794POST /v1/webhook-deliveries/{delivery_id}/redeliver95```96Keep failed deliveries ≥ 30 days. Auto-disable endpoints failing every97delivery for 7 consecutive days; email the owner at 24 h, 72 h, and on98disable.99100## Gotchas101102- **Signing the parsed-then-reserialized body** — key ordering and whitespace103 differ; always HMAC the raw bytes you send/receive.104- **Redirects:** don't follow 3xx on delivery (SSRF vector + signature is now105 going somewhere unverified). Treat as failure.106- **SSRF on subscription:** validate subscriber URLs (https only, no private107 IP ranges, resolve-and-check at send time too — DNS can change).108- **Retrying 4xx forever:** a 410 Gone should disable the endpoint109 immediately; a 401/403 after working deliveries usually means the consumer110 rotated secrets — alert, don't hammer.111- **Ordering promises creep into docs via examples** — audit docs so every112 example shows dedup + unordered handling.113- **One shared secret for all endpoints of a customer** — breach of one114 endpoint burns all; scope secrets per endpoint.115