Webhooks
Complete guide to setting up, receiving, and handling Recur webhook events.
Webhooks Guide
Webhooks let Recur push real-time notifications to your server when subscription events occur — payments, cancellations, failures, and more. This guide covers everything from setup to production best practices.
Setup
Option A: Via Dashboard (Recommended)
- Go to Dashboard → Apps → [Your App]
- Find the Webhook section
- Click Add Endpoint
- Enter your HTTPS URL (e.g.,
https://your-server.com/webhooks/recur) - Copy the webhook secret (shown once — store it securely)
Each app supports one webhook endpoint. To change it, delete the existing one and create a new one.
Option B: Via API
How It Works
Payload Format
Every webhook delivers a JSON body with this structure:
Field Reference
| Field | Type | Description |
|---|---|---|
id | string | Unique event ID — use for deduplication |
type | string | Event type (see table below) |
createdAt | ISO 8601 | When the event was created |
data.subscriptionId | string | Internal subscription ID |
data.subscriptionPda | string | On-chain PDA address (Base58) |
data.planId | string | Plan this subscription belongs to |
data.planName | string | Human-readable plan name |
data.subscriberWallet | string | Subscriber's Solana wallet (Base58) |
data.merchantWallet | string | Merchant's Solana wallet (Base58) |
data.amountGross | string | Total debited (USDC base units, 6 decimals) |
data.platformFee | string | Recur platform fee deducted |
data.amountNet | string | Net amount received by merchant |
data.txSignature | string | Solana transaction signature (for payment events) |
data.nextPaymentDue | ISO 8601 | Next billing date (for payment events) |
Note: Not all fields are present on every event type. Cancel events won't have
amountGross/txSignature. Checkevent.typebefore accessing payment-specific fields.
Event Types
| Event | When it fires | Key data fields |
|---|---|---|
subscription_created | Subscription registered after on-chain tx confirms | subscriptionId, planId, subscriberWallet |
payment_success | Keeper collected a payment successfully | amountGross, amountNet, txSignature, nextPaymentDue |
payment_failed | Payment attempt failed (insufficient balance or delegation exhausted) | subscriptionId, subscriberWallet |
cancel_requested | Subscriber or merchant called request_cancel | subscriptionId, cancelRequestedAt |
cancel_finalized | Prepaid period elapsed, PDA closed on-chain | subscriptionId |
cancel_forced | Merchant force-cancelled a subscription | subscriptionId |
delegation_revoked | Subscriber revoked SPL token delegation | subscriptionId, subscriberWallet |
Signature Verification
Every webhook includes HMAC-SHA256 signature in the X-Recur-Signature header. Always verify before processing.
The signed content is "${timestamp}.${rawBody}" where:
timestamp= value ofX-Recur-Timestampheader (Unix seconds)rawBody= raw request body as string
Manual Verification (without SDK)
Implementation Examples
Express
Next.js App Router
Fastify
Retry Policy
If your endpoint doesn't respond with a 2xx status within 5 seconds, Recur retries:
| Attempt | Delay after failure |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 12 hours |
After 5 retries (6 total attempts): The delivery is marked as permanently failed. No further retries are attempted for that specific event.
Handling Retry Exhaustion
To avoid missing events after retry exhaustion:
-
Monitor delivery status via the API:
-
Poll as fallback — periodically query subscriptions/transactions to catch any missed events:
-
Set up alerting — if your webhook endpoint is consistently failing, investigate immediately.
Best Practices
Security
- Always verify signatures — never process unverified webhooks
- Use HTTPS — webhook URLs must be HTTPS in production
- Keep secrets secure — store webhook secrets in environment variables, not code
Reliability
- Respond quickly — return
2xxwithin 5 seconds, process asynchronously if needed - Deduplicate events — use
event.idto prevent double-processing on retries - Be idempotent — the same event delivered twice should produce the same result
Debugging
- Log raw payloads in development for debugging
- Use webhook.site for testing — verify Recur is sending events before writing handler logic
- Check delivery logs in the dashboard for failed deliveries
Testing Locally
Use a tunnel service to expose your local server:
Then trigger a test event by creating a subscription on devnet — the Keeper will process it and fire a webhook.