> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.credit.aarthiklabs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.credit.aarthiklabs.com/_mcp/server.

# Web-Hooks (V1)

> Receive signed journey snapshot events across Aarthik Labs experiences.

Aarthik Labs uses one shared web-hook system across borrower experiences.

Hosted Experience and Hybrid Experience use the same web-hook contract. Your back-end can build one receiver and handle events consistently across both Aarthik-powered credit journeys.

Web-hooks are back-end-to-back-end notifications. Use them as the durable signal for journey state, reconciliation, support workflows, notifications, analytics, and post-disbursal servicing.

For Hybrid Experience applications enabled for the cleaner V2 payload shape, see [Web-Hooks (V2)](/documentation/web-hooks-v-2). The delivery headers, signing model, and `journey.snapshot` event type stay the same; the body shape is different.

## Where This Applies

| Experience        | How web-hooks apply                                                                                                                                  |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| Hosted Experience | Aarthik Labs owns the borrower-facing journey after your app opens the embedded URL. Web-hooks keep your back-end updated.                           |
| Hybrid Experience | Your product owns the borrower UI until offer selection, then the borrower may move into Hosted Experience. Web-hooks continue across both surfaces. |

Your receiver does not need to be split by experience. Use `origin_experience`, `current_experience_surface`, `tenant_id`, `application_id`, and `borrower.journey.type` to route events inside your own systems.

## Delivery Contract

| Item           | Value                                                                           |
| -------------- | ------------------------------------------------------------------------------- |
| Method         | `POST`                                                                          |
| Content type   | `application/json`                                                              |
| Event type     | `journey.snapshot`                                                              |
| Receiver       | Your back-end-owned HTTPS web-hook URL                                          |
| Authentication | HMAC signature using the shared web-hook secret configured for your application |
| Body           | Canonical Aarthik Labs OutBound journey snapshot                                |
| Delivery model | At-least-once delivery; your receiver should be idempotent                      |

## Subscription APIs

Use the web-hook subscription APIs from your back-end to manage the callback URL for an application. Authenticate with the platform API key for the same tenant/application scope.

| Action             | Endpoint                                                                          |
| ------------------ | --------------------------------------------------------------------------------- |
| Read Configuration | `GET /api/tenants/{tenantID}/applications/{applicationID}/webhooks/configuration` |
| Subscribe          | `POST /api/tenants/{tenantID}/applications/{applicationID}/webhooks/subscribe`    |
| Un-Subscribe       | `POST /api/tenants/{tenantID}/applications/{applicationID}/webhooks/unsubscribe`  |

Read Configuration returns the currently active web-hook status and callback URL for the application.

Subscribe accepts an HTTPS `url` and enables delivery for `journey.snapshot` events.

```json
{
  "url": "https://partner.example.com/webhooks/aarthik-labs"
}
```

Un-Subscribe does not require a request body. It disables delivery for the application, clears the callback URL, and disables the stored signing secret. After this call succeeds, Aarthik Labs stops sending web-hooks for that tenant/application until Subscribe is called again.

## Delivery Headers

| Header                   | Purpose                                                                         |
| ------------------------ | ------------------------------------------------------------------------------- |
| `X-AL-Event-ID`          | Stable identifier for the journey snapshot event. Store this for deduplication. |
| `X-AL-Tenant-ID`         | Tenant that owns the journey.                                                   |
| `X-AL-Application-ID`    | Application that owns the web-hook configuration.                               |
| `X-AL-Webhook-Timestamp` | Timestamp used while computing the signature.                                   |
| `X-AL-Webhook-Signature` | Signature value in the format `t=<timestamp>,v1=<hmac_sha256_hex>`.             |

## Payload Shape

The request body is the latest OutBound snapshot for the borrower journey. It includes experience metadata, tenant/application identifiers, borrower profile data, journey status, lender state, offer state, and repayment schedule data when available.

A shortened example:

```json
{
  "origin_experience": "HYBRID",
  "current_experience_surface": "HOSTED",
  "handoff": {
    "state": "CONSUMED",
    "target_offer_id": "offer_123",
    "target_journey_id": "journey_123",
    "target_hosted_path": "/lab/loan-details",
    "issued_at": "2026-05-04T10:00:00.000Z",
    "consumed_at": "2026-05-04T10:02:00.000Z",
    "expired_at": null
  },
  "tenant_id": "tenant_123",
  "application_id": "application_123",
  "borrower": {
    "id": "borrower_123",
    "provider_id": "customer_001",
    "profile": {
      "personal": {
        "pan": "ABCDE1234F",
        "panName": "Example Customer",
        "dob": "1992-01-31",
        "gender": "male",
        "personalemail": "customer@example.com",
        "contactNumber": "+919999999999"
      },
      "work": {
        "employmentType": "salaried",
        "officialemail": null,
        "income": 750000,
        "companyName": "Example Pvt Ltd",
        "udyamNumber": null
      },
      "address": {
        "addressL1": "Address line 1",
        "addressL2": null,
        "city": "Mumbai",
        "state": "Maharashtra",
        "pincode": "400001"
      }
    },
    "journey": {
      "id": "journey_123",
      "type": "PERSONAL_LOAN",
      "status": "OFFER_ACCEPTED",
      "selection_state": "LOCKED",
      "activity_status": "ACTIVE",
      "decision_status": "APPROVED",
      "lender_mode": "ONLINE",
      "kyc_status": "SUCCESS",
      "emandate_status": "PENDING",
      "esign_status": "PENDING",
      "change_in_offer": false,
      "borrower_preferred_amount": 300000,
      "borrower_preferred_tenure": 12,
      "lenders": [],
      "offers": []
    }
  }
}
```

The exact fields that are populated depend on the credit product, journey stage, lender mode, borrower progress, and whether the borrower has crossed from one experience surface to another.

## ONDC Transaction IDs

ONDC transaction IDs appear in two shapes:

* `borrower.journey.ondc_transaction_ids` is the lender-wise map keyed by ONDC BPP ID. Use it to reconcile available lender tracks once lender discovery has produced them.
* `borrower.journey.ondc_transaction_id` is the selected-track transaction ID. It stays `null` until an offer is locked to the journey.
* `borrower.journey.offers[].ondc_transaction_id` is populated only for the selected locked offer. It stays `null` before lock and for sibling offers that were not selected.

Do not treat the singular journey or offer fields as a discovery-time ONDC ID. Before offer lock, use `ondc_transaction_ids` for lender-wise reconciliation and expect the singular fields to be `null`.

## Experience Fields

| Field                        | Meaning                                                                                                                        |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `origin_experience`          | The experience where the journey originally started. For the experiences documented here, this is `HOSTED` or `HYBRID`.        |
| `current_experience_surface` | The surface that currently owns borrower progression. For Hybrid journeys, this can move from `API` to `HOSTED` after handoff. |
| `handoff`                    | Metadata for a cross-surface handoff. It is `null` when no handoff has happened yet.                                           |

## Lifecycle Statuses

The snapshot includes the latest public journey status. Do not assume every journey will emit every status; the path depends on product, lender, eligibility, redirect behavior, and post-disbursal actions.

| Status              | Meaning                                                                                                |
| ------------------- | ------------------------------------------------------------------------------------------------------ |
| `STARTED`           | The borrower journey was created or resumed.                                                           |
| `NOT_PROFILED`      | Borrower profiling did not complete. The payload stays minimal and does not imply offer discovery ran. |
| `PROFILED`          | Borrower profile information has been captured for offer discovery.                                    |
| `OFFERED`           | One or more offers or actionable offer states are available.                                           |
| `NOT_OFFERED`       | Offer discovery completed without an eligible offer.                                                   |
| `OFFER_ACCEPTED`    | The selected offer moved into downstream acceptance.                                                   |
| `LENDER_REDIRECTED` | The borrower entered a lender-managed redirect step.                                                   |
| `REJECTED`          | The selected-offer journey ended without disbursal.                                                    |
| `DISBURSED`         | The loan journey completed successfully and disbursal was reached.                                     |

## Delivery Flow

```mermaid
sequenceDiagram
  participant Platform as Aarthik Labs Platform
  participant Receiver as Your Web-Hook Receiver
  participant Store as Your Application Store
  participant Ops as CRM, Support, Notifications

  Platform->>Receiver: POST journey.snapshot
  Receiver->>Receiver: Verify timestamp and signature
  Receiver->>Receiver: Deduplicate by X-AL-Event-ID
  Receiver->>Store: Persist latest journey snapshot
  Receiver-->>Platform: Return 2xx after accepting the event
  Store->>Ops: Update internal workflows
```

## Signature Verification

Every delivery includes `X-AL-Webhook-Timestamp` and `X-AL-Webhook-Signature`.

To verify the signature:

1. Read the timestamp from `X-AL-Webhook-Timestamp`.
2. Parse the `v1` value from `X-AL-Webhook-Signature`.
3. Canonicalize the parsed JSON body by sorting object keys recursively and removing `undefined` values.
4. Compute `HMAC-SHA256` over `<timestamp>.<canonical-json-body>` using your shared web-hook secret.
5. Compare the computed digest with the `v1` digest using a timing-safe comparison.

Reject the event if the signature is missing, invalid, or outside the timestamp tolerance you enforce.

## Receiver Checklist

Your web-hook endpoint should:

* accept `POST` requests over HTTPS
* verify the Aarthik Labs signature before trusting the payload
* deduplicate using `X-AL-Event-ID`
* persist the snapshot against your borrower, lead, application, or loan record
* process events idempotently
* return `2xx` only after the event has been accepted for processing
* keep long-running downstream work outside the request-response path

## Polling And Web-Hooks

Frontend polling, bridge events, or browser callbacks can help your UI react while the borrower is actively on screen. Web-hooks are the back-end source of truth for events that may happen after the borrower closes the screen, completes an external lender step, returns later, or moves into post-disbursal servicing.

## Operational Notes

* Web-hook configuration is application-scoped.
* The receiver URL must use HTTPS.
* Failed deliveries can be retried. Treat delivery as at-least-once, not exactly-once.
* Network failures, timeouts, rate limits, and temporary server errors should be handled idempotently by your receiver.
* Keep borrower notifications separate from delivery acknowledgement so a retry does not accidentally notify a borrower twice.
* Monitor delivery failures and reconciliation gaps so your internal state does not silently drift from the Aarthik Labs journey state.