Async Verification with Webhooks
Run verifications asynchronously and receive results via signed webhook delivery — ideal for high-throughput workflows where you need to queue many verifications and process results as they arrive.
Prerequisites
- An API key for calling verification endpoints. See Quickstart or API Keys.
- A Bearer token for registering webhooks. See Authentication.
- A publicly reachable HTTPS endpoint to receive webhook events.
Note: In sandbox, async verifications complete within a few seconds. In live, completion time depends on real provider response times.
How async mode works
1. You submit a verification with "mode": "async"
│
▼
2. Lira returns immediately: { "status": "pending", "id": "ver_..." }
│
▼
3. Lira queues and processes the verification asynchronously
│
├── success → status: success + verified data
└── failure → status: failed + error.code
│
▼
4. Lira POSTs a signed event payload to your registered webhook URL
│
▼
5. Your server verifies the X-Signature header and processes the result
Store the id from the pending response — you'll need it to correlate the incoming webhook event with your original request.
Step 1 — Register a webhook
Use your Bearer token to register an HTTPS endpoint that will receive verification events.
curl -X POST https://api.lira.com/api/v1/client/webhooks \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/lira",
"events": ["verification.completed", "verification.failed"]
}'
Response 201 Created
{
"id": "WEBHOOK_ID",
"url": "https://yourapp.com/webhooks/lira",
"events": ["verification.completed", "verification.failed"],
"status": "active",
"createdAt": "2026-03-09T10:00:00.000Z"
}
Important: Store your webhook signing secret securely in an environment variable before proceeding. You'll use it to verify the
X-Signatureon every incoming delivery. See Webhooks for full webhook management including how to rotate the secret.
Step 2 — Submit an async verification
Add "mode": "async" to any verification request to queue it for asynchronous processing.
Bank account
curl -X POST https://api.lira.com/api/v1/verify/account \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountNumber": "0123456789",
"country": "NG",
"bankCode": "044",
"mode": "async"
}'
Phone number
curl -X POST https://api.lira.com/api/v1/verify/phone \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phoneNumber": "08012345678",
"country": "NG",
"mode": "async"
}'
Immediate response — verification is queued
{
"id": "ver_a1b2c3d4-...",
"status": "pending",
"verificationType": "ACCOUNT_NUMBER",
"identifier": "0123456789",
"country": "NG"
}
Store the id (VERIFICATION_ID) in your database alongside the request. You'll use it to match the incoming webhook event to your original record.
Step 3 — Receive the webhook event
When the verification completes, Lira sends a POST request to your registered endpoint with the result.
verification.completed
{
"event": "verification.completed",
"verificationId": "ver_a1b2c3d4-...",
"organizationId": "org_...",
"timestamp": "2026-03-09T10:05:00.000Z",
"data": {
"status": "success",
"verified": true,
"accountNumber": "0123456789",
"accountName": "Jane Doe",
"bankCode": "044",
"bankName": "Access Bank"
}
}
verification.failed
{
"event": "verification.failed",
"verificationId": "ver_a1b2c3d4-...",
"organizationId": "org_...",
"timestamp": "2026-03-09T10:05:00.000Z",
"data": {
"status": "failed",
"verified": false,
"error": {
"code": "ACCOUNT_NOT_FOUND",
"message": "No account found for the provided details"
}
}
}
Step 4 — Verify the signature
Every webhook request from Lira includes an X-Signature header. Always verify it before processing the event to confirm the payload came from Lira and has not been tampered with.
Header format:
X-Signature: sha256=<hex-signature>
The signature is computed as HMAC-SHA256 over the raw request body using your webhook secret.
Warning: Never process a webhook payload without first verifying the
X-Signature. Reject any request with an invalid or missing signature with401.
Node.js
const crypto = require('crypto');
app.post('/webhooks/lira', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-signature'];
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
const sigBuf = Buffer.from(signature);
const expBuf = Buffer.from(expected);
if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
// Return 200 immediately — process the event in a background job
res.status(200).send('ok');
if (event.event === 'verification.completed') {
const { verificationId, data } = event;
// update your database with the result
console.log(`Verification ${verificationId} completed:`, data.accountName);
}
if (event.event === 'verification.failed') {
const { verificationId, data } = event;
console.log(`Verification ${verificationId} failed:`, data.error.code);
}
});
Python
import hmac
import hashlib
@app.route('/webhooks/lira', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Signature', '')
raw_body = request.get_data()
expected = 'sha256=' + hmac.new(
WEBHOOK_SECRET.encode(), raw_body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
return 'Invalid signature', 401
event = request.json
# Return 200 immediately — queue for background processing
response = make_response('ok', 200)
if event['event'] == 'verification.completed':
verification_id = event['verificationId']
data = event['data']
# update your database with the result
print(f"Verification {verification_id} completed: {data.get('accountName')}")
elif event['event'] == 'verification.failed':
verification_id = event['verificationId']
error = event['data']['error']
print(f"Verification {verification_id} failed: {error['code']}")
return response
Step 5 — Poll for the result (optional)
If you prefer polling over webhooks, or as a fallback when webhook delivery fails, use the verification VERIFICATION_ID to check the current status:
curl https://api.lira.com/api/v1/verify/VERIFICATION_ID \
-H "X-API-Key: YOUR_API_KEY"
Poll until status is no longer pending. Use exponential backoff between polls — do not poll in a tight loop.
Step 6 — Inspect delivery history
Check whether Lira successfully delivered an event to your endpoint:
curl "https://api.lira.com/api/v1/client/webhooks/WEBHOOK_ID/deliveries" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Filter for failed deliveries only:
curl "https://api.lira.com/api/v1/client/webhooks/WEBHOOK_ID/deliveries?status=failed" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
If your endpoint is unreachable or returns a non-2xx status, Lira retries automatically with exponential backoff — up to 5 attempts over approximately 40 minutes.
Best practices
Respond immediately. Return
200 OKright away and process the event in a background job or queue. Long-running processing in the synchronous request handler causes timeouts and triggers unnecessary retries.
Deduplicate events. Lira may deliver the same event more than once when retrying. Store the delivery
idand skip processing if you have already handled it.
Always verify signatures. Never process a payload without confirming the
X-Signature. Use constant-time comparison (timingSafeEqual/compare_digest) to prevent timing attacks. Reject invalid signatures with401.
Re-enable failed webhooks. If your endpoint is down for an extended period and all retry attempts are exhausted, the webhook status becomes
failedand no further deliveries are made. Re-enable it:
curl -X PATCH https://api.lira.com/api/v1/client/webhooks/WEBHOOK_ID \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "status": "active" }'
Summary
| Step | What happens |
|---|---|
Submit with "mode": "async" |
Lira queues the verification and returns status: pending immediately |
| Lira processes the request | Provider lookup happens asynchronously |
| Webhook delivery | Lira POSTs a signed event to your registered endpoint |
| Your server verifies the signature | Reject any request with an invalid X-Signature with 401 |
| Process the result | Look up the verificationId in your database and update your records |
| Failures are retried automatically | Up to 5 attempts with exponential backoff |