Strimz

Webhooks

Every event Strimz fires and how to verify signatures.

Webhooks

Signature

Every webhook is signed with HMAC-SHA256 and sent as the Strimz-Signature header:

Strimz-Signature: t=1714003200,v1=ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef

t is the Unix timestamp at signing time. Receivers should:

  1. Re-compute hmac_sha256(secret, "${t}.${rawBody}")
  2. Compare timing-safe to v1
  3. Reject if |now - t| > 300s (replay protection window)

The reference implementation lives in @strimz/sdkverifyWebhookSignature.

Headers

HeaderDescription
Strimz-Signaturet=<unix>,v1=<hex>
Strimz-Event-Idevt_… , globally unique
Strimz-Event-Typedotted name, e.g. payment.completed
X-Strimz-Delivery-Idwhdl_… , idempotency key for at-least-once delivery

Events

EventFired when
payment.createdA PaymentSession is created
payment.completedAn on-chain payment is confirmed for a session
payment.failedA session expires or fails
subscription.createdA subscription lands on-chain
subscription.chargedA periodic charge succeeded
subscription.charge_failedA charge attempt failed (insufficient funds, revoked approval, etc.)
subscription.cancelledThe merchant or customer cancelled
subscription.lapsedGrace period exceeded. Customer didn't pay
refund.createdRefund intent created
refund.completedRefund tx confirmed on-chain
invoice.created / invoice.paid / invoice.overdueInvoice lifecycle
agent.action_executed / agent.job_*AutoPay Agent activity

Retry policy

Strimz retries with exponential backoff (1m → 5m → 30m → 2h → 12h, 5 attempts). After max attempts:

  • The delivery is marked permanently_failed
  • The merchant is emailed
  • 5+ permanent failures within 24h on the same endpoint → endpoint auto-disabled

Durability behind the scenes

A few details about how the delivery pipeline is built. You don't need to know any of this to use webhooks, but it's worth understanding if you're evaluating Strimz for production traffic.

Signing secrets are stored as AES-256-GCM ciphertext in Postgres. A database dump leaks ciphertext, not the key used to sign your deliveries. The plaintext lives in Redis as a hot cache for the delivery worker; that way we don't pay the decrypt cost on every send. If Redis is flushed, the scheduler decrypts the column back into the cache on its next boot and webhook delivery resumes with no action on your side.

Every attempt is persisted to the WebhookDelivery table. You get the response code, the response body (truncated to 4 KB), the attempt number, the next-attempt time, and the last error, all visible in your dashboard. Every attempt also carries a unique X-Strimz-Delivery-Id header so your endpoint can deduplicate on it.

If your endpoint goes dark for a while (five minutes, an hour, half a day) the retry schedule covers it. If you're down for the entire twelve-and-a-half-hour window, the delivery lands as permanently_failed, and you can replay it from the dashboard once you're back up.

On this page