Skip to main content
TapRails sends signed HTTP 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

  1. Navigate to Dashboard → Settings → Webhooks
  2. Enter your publicly accessible HTTPS endpoint URL
  3. Copy the generated Webhook Secret — you’ll need it to verify signatures
Your endpoint must use HTTPS and be reachable from the internet. Local development servers are not reachable by default — use a tunnel tool like ngrok or Cloudflare Tunnel during testing.

Required response behaviour

TapRails considers a webhook delivery successful when your endpoint returns any 2xx 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.
Respond with 200 OK immediately, then process the event asynchronously. Never perform long-running work (DB writes, external calls) before sending your response — you risk hitting the 10-second timeout.

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

After 5 failed retries (6 total attempts), the webhook log is marked permanently failed and no further automatic retries are attempted. You can still trigger a manual retry from the dashboard or API at any time.
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:

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.
From the API:

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:
The cron endpoint itself is protected with a CRON_SECRET and is typically called by a scheduler such as Vercel Cron, Railway Cron, or a GitHub Actions schedule:
If you are self-hosting TapRails, you must configure this cron job for automatic retries to work. Without it, only manual retries via the dashboard or API will function.

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

Verification examples

Always read the raw request body before parsing JSON. Many frameworks (e.g. Express with json() middleware) will parse and re-serialize the body, which can change byte order or whitespace and break signature verification.

Payload Structure

All webhook payloads share a common envelope:

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


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:
  1. Navigate to Dashboard → Payments → Simulate
  2. Select a merchant and amount
  3. TapRails fires payment.createdpayment.processingpayment.confirmed in 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:
Example log entry response:

Security Best Practices

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.
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.
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.
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.
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.