Challanwala logo
Challanwala
Challanwala API Docs

Webhooks

Configure signed webhook delivery for challan search and payment-link events.

What webhooks are for

Webhooks let your integration receive lifecycle updates without polling every workflow step. They are particularly useful for:

  • challan search completion or failure monitoring
  • payment-link creation notifications
  • payment success, failure, and expiry handling

Configure your webhook

From the organization API settings area, your admin can:

  1. Save a webhook URL.
  2. Activate or deactivate delivery.
  3. Rotate the webhook secret.
  4. Send a test event.

For the testing replica, the same workflow is exposed through staging-only API routes documented in the Testing guide.

Store the webhook secret securely

When the webhook secret is rotated, update your receiver before relying on new deliveries. Use the latest secret to verify every signed payload.

Webhook endpoint requirements

Staging webhook URLs must be public HTTPS endpoints. Localhost, private or link-local addresses, and redirecting callback URLs are rejected by the staging API.

Testing lifecycle behavior

In the staging replica, a normal payment-link flow emits payment_link.created and then payment_link.paid almost immediately. Use the testing-only reserved RC numbers if you want deterministic payment_link.failed or payment_link.expired webhook branches instead.

Delivery headers

Webhook headers
FieldTypeRequiredDescription
content-typestringRequiredAlways sent as application/json.
x-cms-eventstringRequiredEvent type, such as payment_link.paid or challan_search.completed.
x-cms-timestampstringRequiredUnix timestamp used in signature verification.
x-cms-signaturestringRequiredHMAC SHA-256 signature in the format sha256=HEX_DIGEST.

Payload envelope

Every webhook body uses the same top-level shape:

Webhook envelope
{"id": "a8e97bbf-0ba5-4ea7-b7cb-11b29efdf641","type": "payment_link.paid","createdAt": "2026-05-13T11:42:08.000Z","data": {  "...": "event-specific fields"}}

Verify the signature

Use the raw request body exactly as received and compute the signature over:

${x-cms-timestamp}.${rawRequestBody}

Example in Node.js:

import crypto from 'node:crypto';

function verifyWebhook({
  timestamp,
  rawBody,
  signatureHeader,
  secret,
}: {
  timestamp: string;
  rawBody: string;
  signatureHeader: string;
  secret: string;
}) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  const received = signatureHeader.replace(/^sha256=/, '');

  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(received, 'hex'),
  );
}

Supported event types

StatusMeaningNotes
webhook.testTest event queued from the CMS settings screen.Use this to verify connectivity and signature handling.
challan_search.completedA challan search completed successfully.Includes rcNumber, challanCount, and newChallansCount.
challan_search.failedA challan search failed.Includes rcNumber, statusCode, and errorMessage.
payment_link.createdA payment link was created.Includes paymentLinkId, vehicleNumber, amount summary, and expiresAt.
payment_link.paidA payment link was paid successfully.Includes paymentLinkId, transactionId, paymentMode, and amount.
payment_link.failedA payment attempt failed.Includes paymentLinkId, orderId, and errorMessage.
payment_link.expiredA payment link expired before payment.Includes paymentLinkId and expiredAt metadata.

Example event payloads

webhook.test
{"id": "8803f57d-6814-41e5-a3da-97f349acf698","type": "webhook.test","createdAt": "2026-05-13T09:02:21.000Z","data": {  "orgId": "bf4cc07b-d395-456a-8aac-e419d9ef10ff",  "sentAt": "2026-05-13T09:02:21.000Z"}}
challan_search.completed
{"id": "418b94b6-74e8-4d82-a474-773e3aeae0c6","type": "challan_search.completed","createdAt": "2026-05-13T09:10:11.000Z","data": {  "rcNumber": "DL01AB1234",  "apiKeyId": "4e4984c0-16c2-4b33-b831-fcbf5485f95d",  "challanCount": 2,  "newChallansCount": 2}}
payment_link.created
{"id": "e398c607-d52d-4828-9755-9351fef99871","type": "payment_link.created","createdAt": "2026-05-13T10:20:00.000Z","data": {  "paymentLinkId": "17de2557-58d6-4f8d-aa2b-fb60074c6a6d",  "vehicleNumber": "DL01AB1234",  "status": "ACTIVE",  "challanCount": 2,  "amount": {    "baseAmount": 1500,    "legalFee": 100,    "gst": 18,    "convenienceFee": 0,    "totalAmount": 1618  },  "expiresAt": "2026-05-15T14:20:00.000Z"}}
payment_link.paid
{"id": "b58bc6f3-8384-4db9-aa7b-40c418ccb4b1","type": "payment_link.paid","createdAt": "2026-05-13T11:42:08.000Z","data": {  "paymentLinkId": "17de2557-58d6-4f8d-aa2b-fb60074c6a6d",  "status": "PAID",  "orderId": "ORD-20260513-0011",  "transactionId": "TXN-845012193",  "paymentMode": "UPI",  "amount": 1618}}

Delivery expectations

  • Treat any non-2xx response as a failed delivery attempt.
  • Do not rely on redirects during webhook delivery; the staging sender will not follow them.
  • Validate the signature before trusting the payload.
  • Store webhook event IDs if you want an idempotency layer in your own system.
  • In testing, expect final payment events to arrive very quickly after payment_link.created.

On this page