SPB Git

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%

# Patterns — Designing Webhooks

# Contents

  • Delivery envelope
  • Signing (producer) and verification (consumer)
  • Retry schedule
  • Consumer receiver checklist
  • Dead-letter and redelivery API
  • Gotchas

# Delivery envelope

json
{
  "delivery_id": "whd_01J8ZC4T9",
  "type": "invoice.paid",
  "created_at": "2026-08-05T14:30:00Z",
  "api_version": "2026-06-01",
  "data": { "invoice_id": "inv_42" }
}

delivery_id is unique per event and STABLE across retries — it is the consumer's dedup key. api_version pins the payload shape per subscriber.

# Signing (producer) and verification (consumer)

Producer:

python
import hmac, hashlib, time

def sign(secret: str, body: bytes) -> str:
    ts = str(int(time.time()))
    msg = ts.encode() + b"." + body
    sig = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
    return f"t={ts},v1={sig}"
# Header: Webhook-Signature: t=1754404200,v1=5257a86...

Consumer:

python
# 300 s = 5 min replay window: generous for clock skew, tight for replays.
MAX_SKEW = 300

def verify(secret, header, body) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    ts, their_sig = parts["t"], parts["v1"]
    if abs(time.time() - int(ts)) > MAX_SKEW:
        return False
    msg = ts.encode() + b"." + body
    ours = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
    return hmac.compare_digest(ours, their_sig)   # constant time

Verify against the RAW request body bytes — any re-serialization (JSON parse then dump) changes the bytes and fails the check.

# Retry schedule

text
attempt  delay      cumulative
1        immediate  0
2        1 min      1 min
3        5 min      6 min
4        30 min     36 min
5        2 h        ~2.6 h
6        12 h       ~14.6 h    → dead-letter after this

Add ±20% jitter to every delay so a consumer recovering from an outage is not hit by a synchronized thundering herd. Timeout per attempt: 10 s.

# Consumer receiver checklist (publish this in your docs)

text
1. Verify Webhook-Signature before parsing (raw body, constant-time compare).
2. Reject t older than 5 minutes.
3. Return 200 immediately; process async (queue). Slow handlers get retried
   and you will process duplicates.
4. Deduplicate on delivery_id (store processed IDs ≥ 24 h).
5. Treat events as unordered; fetch current state from the API when it matters.
6. Do not whitelist our IPs as your only security — verify signatures.

# Dead-letter and redelivery API

text
GET  /v1/webhook-deliveries?status=failed&endpoint_id=we_7
POST /v1/webhook-deliveries/{delivery_id}/redeliver

Keep failed deliveries ≥ 30 days. Auto-disable endpoints failing every delivery for 7 consecutive days; email the owner at 24 h, 72 h, and on disable.

# Gotchas

  • Signing the parsed-then-reserialized body — key ordering and whitespace differ; always HMAC the raw bytes you send/receive.
  • Redirects: don't follow 3xx on delivery (SSRF vector + signature is now going somewhere unverified). Treat as failure.
  • SSRF on subscription: validate subscriber URLs (https only, no private IP ranges, resolve-and-check at send time too — DNS can change).
  • Retrying 4xx forever: a 410 Gone should disable the endpoint immediately; a 401/403 after working deliveries usually means the consumer rotated secrets — alert, don't hammer.
  • Ordering promises creep into docs via examples — audit docs so every example shows dedup + unordered handling.
  • One shared secret for all endpoints of a customer — breach of one endpoint burns all; scope secrets per endpoint.