Skip to content
Lira APILira API

Overview

Webhooks let Lira push real-time notifications to your server when a verification completes or fails, eliminating the need to poll the API and enabling event-driven workflows like KYC approvals, user onboarding triggers, and payment releases.

When a verification finishes processing (in async mode), Lira sends a signed POST request to your registered HTTPS endpoint with the full result. Your server verifies the signature and processes the event.


Before you start

Webhooks are configured in the Lira dashboard — you register endpoints, rotate secrets, and inspect delivery history there. Your application's only job is to receive the signed events and verify them, which is what the rest of this guide covers.

For the full async verification flow (including how to submit async verifications and handle webhook events), see Async Verification with Webhooks.


Events

EventWhen it fires
verification.completedA verification was successfully processed (status: success)
verification.failedA verification attempt failed (status: failed or error)

Register a webhook

Register your endpoint from the dashboard:

  1. Sign in at app.uselira.com and open Webhooks.
  2. Add an endpoint. Enter the HTTPS URL Lira should POST events to, and select the events to subscribe to (verification.completed, verification.failed).
  3. Copy the signing secret. The dashboard shows the webhook's signing secret once, when the endpoint is created. Copy it immediately and store it as an environment variable (e.g. WEBHOOK_SECRET) — you'll need it to verify signatures, and it cannot be recovered later.

URL requirements

  • Must use https://
  • Must be publicly reachable from the internet
  • localhost and private IP ranges are not permitted

Note

During local development, expose your local server with a tunnel such as ngrok or Cloudflare Tunnel so Lira can reach it over HTTPS.


Verifying signatures

Every request Lira sends to your endpoint includes an X-Signature header. Verify it before processing the event to confirm the request came from Lira and the payload has not been tampered with.

Header format

Text
X-Signature: sha256=<hex-signature>

The signature is computed as HMAC-SHA256 over the raw request body using your webhook secret. Always use the raw body, not a parsed JSON object, for signature verification. Parsing and re-serialising JSON can alter whitespace and change the computed hash.

Warning

Always verify signatures before processing any webhook payload. Use constant-time comparison functions (timingSafeEqual in Node.js, compare_digest in Python) to prevent timing attacks. Reject invalid signatures with 401.

Node.js

JavaScript
const crypto = require('crypto');
 
function verifyWebhookSignature(rawBody, signatureHeader, secret) {
  const signature = signatureHeader.replace('sha256=', '');
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
 
  const sigBuf = Buffer.from(signature);
  const expectedBuf = Buffer.from(expected);
 
  if (sigBuf.length !== expectedBuf.length) return false;
  return crypto.timingSafeEqual(sigBuf, expectedBuf);
}
 
// Express handler, use raw body parser so the signature matches
app.post('/webhooks/lira', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-signature'];
 
  if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
 
  const event = JSON.parse(req.body);
  // handle event...
  res.status(200).send('ok');
});

Python

Python
import hmac
import hashlib
 
def verify_webhook_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
    signature = signature_header.replace('sha256=', '')
    expected = hmac.new(
        secret.encode(),
        raw_body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)
 
# Flask handler
@app.route('/webhooks/lira', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Signature', '')
 
    if not verify_webhook_signature(request.get_data(), signature, WEBHOOK_SECRET):
        return 'Invalid signature', 401
 
    event = request.json
    # handle event...
    return 'ok', 200

Handling events

Lira expects a 2xx response from your endpoint. Any other status code or a connection timeout is treated as a failed delivery and triggers a retry.

Respond quickly. Return 200 OK immediately and process the event asynchronously in a background job. Long-running handlers cause timeouts and trigger retries.

Be idempotent. Lira may deliver the same event more than once when retrying. Store the delivery id and skip processing if you have already handled it.

Event payload

JSON
{
  "event": "verification.completed",
  "verificationId": "...",
  "organizationId": "...",
  "timestamp": "2026-03-09T10:05:00.000Z",
  "data": {}
}
FieldDescription
eventEvent type: verification.completed or verification.failed
verificationIdThe ID of the verification this event relates to
organizationIdYour organization ID
timestampISO 8601 timestamp of when the event was emitted
dataThe full verification result. Shape matches the GET /verify/:id response.

Retries

If your endpoint does not return a 2xx response, Lira retries with exponential backoff:

AttemptDelay
1Immediate
21 minute
33 minutes
49 minutes
527 minutes

After 5 failed attempts, the webhook status is automatically set to failed and no further deliveries are made. Re-enable it from the Webhooks page in the dashboard.


Managing webhooks

Manage your endpoints from the Webhooks page in the dashboard:

  • Edit an endpoint's URL, subscribed events, or status (active / inactive).
  • Rotate the signing secret. After rotating, update WEBHOOK_SECRET in your application before the old secret stops being used.
  • Disable or delete an endpoint. Disabling pauses deliveries without removing the endpoint; deleting stops all future deliveries permanently.

Delivery history is available both in the dashboard and via the API — see Delivery history below.


Delivery history

Inspect delivery attempts for a webhook programmatically with your API key. You'll need the webhook's ID, shown on the Webhooks page in the dashboard.

List deliveries for a webhook

Terminal
curl "https://api.uselira.com/api/v1/client/webhooks/WEBHOOK_ID/deliveries" \
  -H "X-API-Key: YOUR_API_KEY"

Filter by status (pending, success, failed):

Terminal
curl "https://api.uselira.com/api/v1/client/webhooks/WEBHOOK_ID/deliveries?status=failed" \
  -H "X-API-Key: YOUR_API_KEY"

Get a single delivery

Terminal
curl "https://api.uselira.com/api/v1/client/webhooks/WEBHOOK_ID/deliveries/DELIVERY_ID" \
  -H "X-API-Key: YOUR_API_KEY"

Delivery object

JSON
{
  "id": "del_...",
  "webhookId": "WEBHOOK_ID",
  "verificationId": "...",
  "status": "failed",
  "attempts": 3,
  "lastAttemptAt": "2026-03-09T10:20:00.000Z",
  "payload": {},
  "response": {},
  "createdAt": "2026-03-09T10:05:00.000Z",
  "updatedAt": "2026-03-09T10:20:00.000Z"
}
Delivery statusMeaning
pendingDelivery is queued or in progress
successYour endpoint returned a 2xx response
failedAll retry attempts exhausted without a 2xx response

Troubleshooting

Deliveries are not arriving

  1. Check that your webhook URL uses https:// and is publicly reachable from the internet. Verify it is not behind a VPN or firewall that blocks inbound requests.
  2. Check the endpoint status on the Webhooks page. If it is failed, all retry attempts were exhausted — set it back to active to resume.
  3. Inspect recent deliveries — in the dashboard or via GET /client/webhooks/WEBHOOK_ID/deliveries?status=failed (see Delivery history). The response your endpoint returned is shown for each failed delivery.
  4. Confirm your endpoint returns a 2xx status code. 3xx redirects are not followed by Lira.

Signature verification is failing

  1. Ensure you are computing the HMAC over the raw request body bytes, not a parsed or re-serialised JSON object.
  2. Confirm the WEBHOOK_SECRET environment variable contains the same secret shown in the dashboard for this endpoint.
  3. Use the exact string sha256= prefix when comparing; do not strip it before computing.

Webhook status became failed

Re-enable the endpoint from the Webhooks page in the dashboard. Once re-enabled, Lira delivers events for any new verifications that complete. Events from the period when the webhook was failed are not retroactively delivered — use GET /verify or GET /verify/VERIFICATION_ID to retrieve results for any verifications that completed during the outage.


Next steps