Webhooks
Webhooks let API Express notify your application in real time when async operations complete — bulk verifications, recharge status changes, background jobs. Instead of polling our APIs, your server receives events as they happen.
Overview
Some API Express operations are asynchronous — meaning they don't return a final result immediately. Bulk verifications, background job processing, and settlement completions happen on our side over seconds to minutes.
Webhooks flip that model: instead of your app constantly asking "is it done yet?", we notify your app the moment the operation completes. This is faster, more efficient, and more reliable than polling.
How it works
- You register a webhook URL in your API Express dashboard
- You select which events you want to subscribe to
- When an event occurs, we send an HTTP POST to your URL with the event data
- Your server responds with
2xxto acknowledge receipt - If your server returns a non-2xx response, we retry with exponential backoff
Setting Up Webhooks
Webhook configuration is done through your API Express dashboard. You can also manage webhooks programmatically via our API.
Via dashboard
- Log in to your API Express dashboard
- Navigate to Settings → Webhooks
- Click Add Webhook
- Enter your endpoint URL (must be HTTPS)
- Select the events you want to receive
- Copy the webhook secret for signature verification
- Click Save
Via API
curl -X POST https://api.apiexpress.in/v1/webhooks \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/apiexpress",
"events": ["recharge.completed", "verification.bulk.completed"],
"secret": "whsec_you_generate_this"
}'
Event Types
API Express sends the following event types. Subscribe to only the events your application needs.
Recharge events
| Event | Fires when |
|---|---|
| recharge.completed | A recharge transaction succeeds |
| recharge.failed | A recharge transaction fails |
| recharge.pending | Recharge is pending operator confirmation |
| recharge.refunded | Recharge was reversed and refunded |
Verification events
| Event | Fires when |
|---|---|
| verification.completed | Single verification completes |
| verification.bulk.completed | Bulk verification batch completes |
| verification.failed | Verification returns a definitive failure |
Account & billing events
| Event | Fires when |
|---|---|
| quota.warning | You reach 80% of your monthly quota |
| quota.exceeded | You hit 100% of your monthly quota |
| payment.succeeded | A payment for your account succeeds |
| payment.failed | A payment for your account fails |
Payload Format
Every webhook event has the same envelope structure. The data field varies by event type.
Event envelope
{
"id": "evt_1a2b3c4d5e6f",
"type": "recharge.completed",
"created": 1728139842,
"api_version": "2026-01-01",
"data": {
// Event-specific data
}
}
Example: recharge.completed
{
"id": "evt_1a2b3c4d5e6f",
"type": "recharge.completed",
"created": 1728139842,
"api_version": "2026-01-01",
"data": {
"transaction_id": "TXN928374",
"mobile": "9876543210",
"operator": "Airtel",
"circle": "Maharashtra",
"amount": 199,
"type": "prepaid",
"status": "success",
"ref_id": "YOUR_REF_001",
"completed_at": "2026-10-05T10:30:42Z"
}
}
Example: verification.bulk.completed
{
"id": "evt_9x8y7z6w5v4u",
"type": "verification.bulk.completed",
"created": 1728139842,
"api_version": "2026-01-01",
"data": {
"batch_id": "batch_a1b2c3d4",
"total": 1000,
"successful": 987,
"failed": 13,
"results_url": "https://api.apiexpress.in/v1/verification/batch/batch_a1b2c3d4/results",
"expires_at": "2026-10-12T10:30:42Z"
}
}
Signature Verification
Every webhook request includes an X-APIX-Signature header containing an HMAC-SHA256 signature. Always verify this signature before processing the event — it proves the request came from API Express and hasn't been tampered with.
Signature format
The header looks like this:
X-APIX-Signature: t=1728139842,v1=5d41402abc4b2a76b9719d911017c592...
Where:
t— Unix timestamp of when the signature was generatedv1— HMAC-SHA256 hex digest
Verifying in Node.js
const crypto = require('crypto');
function verifyWebhook(rawBody, signatureHeader, secret) {
// Parse signature header
const parts = signatureHeader.split(',');
const timestamp = parts[0].split('=')[1];
const signature = parts[1].split('=')[1];
// Reconstruct the signed payload
const signedPayload = `${timestamp}.${rawBody}`;
// Compute expected signature
const expected = crypto
.createHmac('sha256', secret)
.update(signedPayload, 'utf8')
.digest('hex');
// Compare securely
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expected, 'hex')
);
}
Verifying in Python
import hmac
import hashlib
def verify_webhook(raw_body, signature_header, secret):
parts = signature_header.split(',')
timestamp = parts[0].split('=')[1]
signature = parts[1].split('=')[1]
signed_payload = f"{timestamp}.{raw_body}"
expected = hmac.new(
secret.encode('utf-8'),
signed_payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
express.raw() middleware for webhook routes.Timestamp tolerance
Reject webhooks where the timestamp is more than 5 minutes old. This prevents replay attacks.
Retry Logic
If your endpoint returns a non-2xx status or times out, we retry the delivery with exponential backoff.
Retry schedule
| Attempt | Delay after previous | Total elapsed |
|---|---|---|
| 1 (initial) | — | 0 |
| 2 | 10 seconds | 10s |
| 3 | 30 seconds | 40s |
| 4 | 2 minutes | 2m 40s |
| 5 | 10 minutes | 12m 40s |
| 6 | 1 hour | ~1h |
| 7 | 6 hours | ~7h |
| 8 (final) | 24 hours | ~31h |
After 8 failed attempts, the event is marked as failed and no further retries occur. You can view failed deliveries in your dashboard and manually retry them.
Best practices for handling retries
- Respond fast. Acknowledge with
200 OKwithin 5 seconds. Process the event asynchronously in the background — don't do heavy work in the request handler. - Handle duplicates. If your server takes longer than 5 seconds, we may retry while your original request is still processing. Use the event
idto deduplicate. - Return the right status. Use
200for success,4xxfor permanent errors (no retry),5xxfor temporary errors (retry). - Log everything. Log incoming webhooks with their IDs and event types so you can trace issues.
Idempotency — handling duplicate events
Retries can cause the same event to be delivered more than once. Guard against this by tracking processed event IDs.
async function handleWebhook(event) {
// Check if we've already processed this event
const exists = await db.webhooks.findOne({ id: event.id });
if (exists) {
return; // Already processed — skip
}
// Process the event
await processEvent(event);
// Mark as processed
await db.webhooks.insert({ id: event.id, processed_at: new Date() });
}
Best Practices
Always verify signatures
Without signature verification, anyone who knows your webhook URL can send fake events to your server. Always verify the X-APIX-Signature header before processing events.
Use HTTPS only
Webhook URLs must use HTTPS. This protects event payloads in transit. Use a valid TLS certificate (Let's Encrypt, AWS ACM, Cloudflare, or a CA-signed cert).
Acknowledge quickly
Return 200 OK within 5 seconds. If your processing takes longer, queue the event for async processing and return immediately.
Handle out-of-order events
In rare cases, events may arrive out of chronological order. Use the created timestamp and any state fields in the payload to reconcile the correct order.
Monitor your webhook endpoint
Set up monitoring for your webhook endpoint — alert on:
- Response time above 5 seconds
- Error rate above 1%
- Signature verification failures (could indicate an attack)
- Duplicate event IDs (indicates a bug in your deduplication logic)
Don't rely on webhooks alone
Webhooks are best-effort delivery. If your endpoint is down for more than 24 hours, events will fail permanently. For critical workflows, also poll the relevant API periodically as a fallback.
Related Documentation
- Authentication — Secure your webhook endpoints
- Error Codes — Handle webhook delivery errors
- Sandbox Testing — Test webhooks safely
- Rate Limits — Understand rate limits on webhook retries
Get Real-Time Notifications on Every Event
Set up your first webhook in minutes. Sign up free, add your endpoint, and start receiving events. No credit card required.