Manages webhook event delivery to registered endpoints.
This GenServer delivers webhook events with:
- Stripe-compatible HMAC SHA256 signing of payloads
- Exponential backoff retry logic (max 5 attempts)
- Detailed delivery attempt tracking in Event object
- Concurrent delivery to multiple endpoints
- Optional synchronous mode for testing
Delivery Modes
By default, webhooks are delivered asynchronously. For testing, you can enable synchronous mode so API calls block until webhooks are delivered:
config :paper_tiger, webhook_mode: :syncIn sync mode, deliver_event_sync/2 is used which blocks until the webhook
is delivered (or fails after all retries).
Architecture
- Async delivery:
deliver_event/2- Queues a delivery task (default) - Sync delivery:
deliver_event_sync/2- Blocks until complete - Signing:
sign_payload/2- Creates Stripe-compatible HMAC SHA256 signature - HTTP client: Uses Req library for reliable, timeout-aware requests
- Retry strategy: Exponential backoff (1s, 2s, 4s, 8s, 16s)
- Tracking: Stores delivery attempts in Event object via Store.Events
Stripe Signature Format
The Stripe-Signature header follows Stripe's format:
Stripe-Signature: t={timestamp},v1={signature}Where:
t= Unix timestamp when webhook was createdv1= HMAC SHA256 signature of "{timestamp}.{payload}" using webhook secret
Examples
# Deliver an event asynchronously (default)
{:ok, _ref} = PaperTiger.WebhookDelivery.deliver_event("evt_123", "we_456")
# Deliver an event synchronously (for testing)
{:ok, :delivered} = PaperTiger.WebhookDelivery.deliver_event_sync("evt_123", "we_456")
# Manually create a signature for testing
signature = PaperTiger.WebhookDelivery.sign_payload("body", "secret")
Summary
Functions
Returns a specification to start this module under a supervisor.
Delivers a webhook event to a specific endpoint.
Delivers a webhook event synchronously, waiting for completion.
Signs a payload using HMAC SHA256 (Stripe-compatible).
Starts the WebhookDelivery GenServer.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
Delivers a webhook event to a specific endpoint.
This function queues the delivery asynchronously. Multiple calls with different webhook_endpoint_ids deliver to all endpoints.
Parameters
event_id- ID of the event to deliver (e.g., "evt_123")webhook_endpoint_id- ID of the webhook endpoint (e.g., "we_456")
Returns
{:ok, reference}- Delivery queued successfully{:error, reason}- Delivery could not be queued
Examples
{:ok, _ref} = PaperTiger.WebhookDelivery.deliver_event("evt_123", "we_456")
Delivers a webhook event synchronously, waiting for completion.
Unlike deliver_event/2, this function blocks until the webhook has been
delivered (or fails after all retries). Use this in test environments where
you need webhooks to be processed before assertions.
Parameters
event_id- ID of the event to deliver (e.g., "evt_123")webhook_endpoint_id- ID of the webhook endpoint (e.g., "we_456")
Returns
{:ok, :delivered}- Webhook delivered successfully{:ok, :failed}- Delivery failed after all retries{:error, reason}- Event or webhook not found
Examples
{:ok, :delivered} = PaperTiger.WebhookDelivery.deliver_event_sync("evt_123", "we_456")
Signs a payload using HMAC SHA256 (Stripe-compatible).
Creates the signature component for the Stripe-Signature header.
The actual signature is computed on "{timestamp}.{payload}".
Parameters
payload- JSON string (or any string data) to signsecret- Webhook secret from the webhook endpoint
Returns
String containing the hex-encoded HMAC SHA256 signature.
Examples
signature = PaperTiger.WebhookDelivery.sign_payload(payload, "whsec_...")
# Returns: "abcd1234..."
@spec start_link(keyword()) :: GenServer.on_start()
Starts the WebhookDelivery GenServer.