POST requests to your configured webhook URL whenever key events occur — payment confirmations, pool deposits, session key changes, and more.
All webhook deliveries are tracked, and failed deliveries are automatically retried with exponential backoff for up to 24 hours. You can also view the full delivery history and trigger manual retries from the dashboard or the API.
Webhooks are optional but strongly recommended for production integrations. Without them, you must poll the API to track payment and pool state changes.
Setup
Configure your endpoint in the Dashboard
- Navigate to Dashboard → Settings → Webhooks
- Enter your publicly accessible HTTPS endpoint URL
- Copy the generated Webhook Secret — you’ll need it to verify signatures
Required response behaviour
TapRails considers a webhook delivery successful when your endpoint returns any2xx HTTP status code within 10 seconds. Any other outcome — a non-2xx response, a timeout, or a network error — marks the delivery as FAILED and schedules an automatic retry.
Delivery & Retry Behaviour
TapRails uses a 5-attempt exponential backoff system to ensure your endpoint receives every event, even if your server is temporarily down.Backoff Schedule
| Attempt | Delay after previous failure |
|---|---|
| 1st (initial send) | Immediate |
| 2nd retry | 5 minutes |
| 3rd retry | 15 minutes |
| 4th retry | 45 minutes |
| 5th retry | 2 hours |
| 6th retry | 6 hours |
The total retry window is approximately 9 hours from the first failed attempt. Ensure your webhook endpoint is recoverable within this window for guaranteed delivery.
Delivery Log
Every delivery attempt — successful or not — is recorded. Each log entry includes:| Field | Description |
|---|---|
status | SUCCESS, FAILED, or PENDING |
attempts | How many delivery attempts have been made |
last_attempt_at | Timestamp of the most recent attempt |
next_retry_at | Scheduled time for the next automatic retry |
response_status | HTTP status code returned by your endpoint |
response_body | First 1,000 characters of your endpoint’s response |
error_message | Network error or timeout message (if applicable) |
Viewing & Managing Deliveries
From the Dashboard:- Navigate to Settings → Webhooks → Delivery Logs to browse all attempts.
- Filter by
status(e.g.FAILED) or event type. - Click Retry on any log entry to dispatch it immediately, regardless of the scheduled retry time.
Automatic Retry Cron Job
Retries are dispatched by a background cron job that runs every 5–15 minutes. It picks up queued retries, processes up to 20 per run, and returns a summary:CRON_SECRET and is typically called by a scheduler such as Vercel Cron, Railway Cron, or a GitHub Actions schedule:
Verifying Webhook Signatures
Every webhook request is signed using HMAC-SHA256 with your Webhook Secret. Always verify the signature before processing the payload.Headers sent with every request
| Header | Description |
|---|---|
X-Webhook-Signature | HMAC-SHA256 hex digest of the raw JSON body |
X-Webhook-Timestamp | ISO 8601 timestamp of when the event was dispatched |
X-Webhook-Event | The event type string (e.g. payment.confirmed) |
User-Agent | TapRail-Webhook/1.0 |
Verification examples
Payload Structure
All webhook payloads share a common envelope:| Field | Type | Description |
|---|---|---|
event | string | Event type identifier |
timestamp | string | ISO 8601 UTC timestamp |
data | object | Event-specific payload (see below) |
Event Reference
Payment Events
payment.created
Fired when a new payment request is created by a merchant device.
payment.processing
Fired when the customer’s device has submitted the transaction to the blockchain. The payment is on-chain but not yet confirmed.
payment.confirmed
Fired when the payment transaction is confirmed on-chain. This is the definitive success signal — use this to fulfil orders.
payment.failed
Fired when a payment transaction reverts or fails on-chain.
payment.expired
Fired when a payment request passes its expiry time without being completed.
Pool Events
Pool events notify you about changes to your company’s USDC liquidity pool, which funds merchant payouts.pool.deposit_received
Fired when USDC is deposited into your treasury wallet. Pool balance is automatically synced.
pool.withdrawal_completed
Fired when USDC is withdrawn from your treasury wallet.
pool.low_balance
Fired when your pool balance drops below the configured low-balance threshold. Use this to trigger an automatic top-up or alert your operations team.
Session Key Events
Session key events track the lifecycle of customer session keys used for gas-free USDC payments.session_key.registered
Fired when a customer successfully registers a session key on their device.
session_key.revoked
Fired when a session key is explicitly revoked (e.g. user logs out or de-authorises a device).
session_key.limit_exceeded
Fired when a payment attempt is blocked because it would exceed the session key’s daily spend limit.
Device Events
device.registered
Fired when a merchant device completes registration with the TapRails SDK.
Complete Event List
| Event | Trigger |
|---|---|
payment.created | A merchant creates a new payment request |
payment.processing | Customer submits payment transaction to chain |
payment.confirmed | Payment transaction confirmed on-chain ✅ |
payment.failed | Payment transaction reverted or failed ❌ |
payment.expired | Payment request expired before completion |
pool.deposit_received | USDC deposited into your treasury wallet |
pool.withdrawal_completed | USDC withdrawn from your treasury wallet |
pool.low_balance | Pool balance fell below configured threshold |
session_key.registered | Customer registered a new session key |
session_key.revoked | Session key was revoked |
session_key.limit_exceeded | Payment blocked by daily spend limit |
device.registered | Merchant device completed SDK registration |
Testing Webhooks
Local development with ngrok
Your endpoint must be publicly reachable over HTTPS. For local development, use a tunnel:Simulating a payment (Test Mode)
In test mode, you can trigger a full end-to-end payment cycle from the dashboard to validate your webhook handler:- Navigate to Dashboard → Payments → Simulate
- Select a merchant and amount
- TapRails fires
payment.created→payment.processing→payment.confirmedin sequence
Debugging failed deliveries
In the Dashboard: Navigate to Settings → Webhooks → Delivery Logs. Each entry shows status, HTTP response code, response body (first 1,000 chars), and the next scheduled retry time. Click Retry to re-dispatch immediately. Via API:Rotating your webhook secret during testing
Security Best Practices
Always verify the signature
Always verify the signature
Never process a webhook payload without first verifying the
X-Webhook-Signature header using your webhook secret. Any request that fails verification should be rejected with a 401.Respond immediately — process asynchronously
Respond immediately — process asynchronously
TapRails aborts delivery if your endpoint doesn’t respond within 10 seconds. Respond with
200 OK right away and push the event to an internal queue (e.g. Redis, BullMQ, SQS) for background processing. If your server is down, the automatic retry system will re-attempt delivery up to 5 more times over approximately 9 hours.Make your handlers idempotent
Make your handlers idempotent
The same event will be delivered more than once if retries are triggered. Use the
payment_id, tx_hash, or withdrawal_id in the payload as an idempotency key — check whether you’ve already processed an event before acting on it. A simple SET payment_id = processed in your database is sufficient.Rotate your webhook secret periodically
Rotate your webhook secret periodically
Treat your webhook secret like a password. Rotate it in the Dashboard periodically and update your server environment variable accordingly. Secrets are never exposed after initial generation.
Validate the event type before acting
Validate the event type before acting
Don’t rely solely on the
X-Webhook-Event header — always read the event field from the parsed JSON body and branch your logic based on it.
