Introduction

WelcomeServices map

Guides

AcceptSpend

Legal

SecurityPrivacyTerms

PreviousPoliciesNextOverview
BlogPrivacyTerms

Webhooks

Register HTTPS callbacks for payment and funding events. Delivery is signed, SSRF-safe, retried via an outbox, and verifiable with the secret returned on create.


Same flow in MCP: rill_webhooks

Auth

Owner Supabase JWT required. Send Idempotency-Key on create. Save the signing secret from the create response, it is required to verify X-Rill-Signature on every delivery.

Authorization: Bearer <owner JWT>

Featured endpoints

Register a webhook

POST /webhooks

Register a callback URL for events such as payment.succeeded and funding.paid. Localhost callbacks are allowed outside production. List registered endpoints with GET /webhooks.

ParameterTypeRequiredDescription
urlstringyesHTTPS callback URL
eventsstring[]noOptional event allowlist; omit to receive the default set

Request body

body
{
  "url": "https://example.com/hooks/rill",
  "events": ["payment.succeeded", "funding.paid"]
}

What to save

Save the webhook id and signing secret. Verify every delivery as HMAC-SHA256 of `{X-Rill-Webhook-Id}.{X-Rill-Timestamp}.{raw body}` (hex), header X-Rill-Signature: v1,<hex>, plus X-Rill-Timestamp within 300 seconds. Use the raw bytes; do not re-serialize JSON.

  • Catalog events with GET /webhooks/events.
  • Test with POST /webhooks/:id/test (signed {test:true} event); retry with …/deliveries/:deliveryId/retry.
  • MCP twin: rill_webhooks action=create|list|test|delete.

All endpoints

  • POST/webhooksRegister URL (Idempotency-Key)
  • GET/webhooksList endpoints
  • GET/webhooks/eventsEvent catalog
  • DELETE/webhooks/:idDelete endpoint
  • GET/webhooks/:id/deliveriesDelivery log
  • POST/webhooks/:id/testSend test event
  • POST/webhooks/:id/deliveries/:deliveryId/retryRetry delivery

Signing and events

  • Sign: HMAC-SHA256 over `{X-Rill-Webhook-Id}.{X-Rill-Timestamp}.{raw body}`, hex digest, header X-Rill-Signature: v1,<hex> (comma, not equals). Reject if |now - timestamp| > 300 seconds.
  • HTTP body is the signed JSON envelope {id, type, created_at, data}. X-Rill-Webhook-Id is envelope.id. Verify the raw body bytes.
  • payment.succeeded data includes ok, type, rail (mpp|x402|rill), receipt_id, resource_id, short_id, path_or_tool, metadata (the pay link SKU: sku_kind one_shot|credit_topup, credits, credit_unit, plus anything you stored), seller_id, amount, platform_fee, stripe_payment_intent_id, seller_balance.
  • Example envelope: {"id":"evt_…","type":"payment.succeeded","created_at":"2026-09-09T12:00:00.000Z","data":{"ok":true,"type":"resource","rail":"x402","receipt_id":"rcpt_…","resource_id":"res_…","short_id":"AB12CD","path_or_tool":"/get_full_dossier","metadata":{"sku_kind":"one_shot"},"seller_id":"sel_…","amount":"34.00","platform_fee":"0.00","stripe_payment_intent_id":"pi_…"}}
  • Credit top-up example data: {"rail":"mpp","path_or_tool":"/credits/500","metadata":{"sku_kind":"credit_topup","credits":500,"credit_unit":"api_calls"},"amount":"25.00"}. Add metadata.credits to the buyer identified by your own request context; the receipt_id is your dedupe key.
  • Node verify: createHmac('sha256', secret).update(`${eventId}.${timestamp}.${rawBody}`).digest('hex') then timingSafeEqual against the v1, prefix.
  • Events include payment.succeeded, payment.failed, transfer.received, funding.paid, withdrawal.paid, withdrawal.failed, vw.revoked, allowance.reset, seller.withdrawn, resource.updated.