> 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 (V2)

> Receive clean Hybrid Experience journey lifecycle snapshots for offers, Account Aggregator, offer acceptance, lender redirection, rejection, and disbursal.

Web-Hooks (V2) is the cleaner customer-facing web-hook payload for Hybrid Experience journey lifecycle updates.

It keeps the same delivery model as existing Aarthik Labs web-hooks:

* Aarthik Labs sends `POST` requests to your configured HTTPS web-hook URL.
* The event type remains `journey.snapshot`.
* Deliveries are signed with the existing Aarthik Labs web-hook signature headers.
* Delivery is at least once, so your receiver should be idempotent.

The main difference is the body. V2 sends a cleaner, stage-aware payload that includes only the information that is useful for the lifecycle stage being delivered.

In this page, `AA` means Account Aggregator.

V2 is opt-in and enabled by Aarthik Labs at the application level. Applications that are not enabled continue receiving the existing web-hook payload.

## Delivery Contract

| Item            | Value                                                                           |
| --------------- | ------------------------------------------------------------------------------- |
| Method          | `POST`                                                                          |
| Content type    | `application/json`                                                              |
| Event type      | `journey.snapshot`                                                              |
| Delivery model  | At least once                                                                   |
| Receiver        | Your back-end-owned HTTPS web-hook URL                                          |
| Authentication  | HMAC signature using the shared web-hook secret configured for your application |
| Idempotency key | `X-AL-Event-ID`                                                                 |

## Delivery Headers

Every V2 delivery uses the same delivery headers as existing web-hooks.

| Header                   | Meaning                                                                           |
| ------------------------ | --------------------------------------------------------------------------------- |
| `X-AL-Event-ID`          | Stable identifier for the delivered event. Store it and use it 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>`.               |

For signature verification details, use the same process described in [Web-Hooks](/documentation/web-hooks-v-1#signature-verification).

## Top-Level Envelope

Every V2 payload uses this top-level envelope.

```json
{
  "request_id": "event-id",
  "correlation_id": "event-id",
  "timestamp": "2026-05-12T10:10:00.000Z",
  "tenant_id": "tenant-id",
  "application_id": "application-id",
  "tenantID": "tenant-id",
  "applicationID": "application-id",
  "data": {
    "status": "OFFERED"
  },
  "error": null
}
```

| Field                             | Meaning                                                          |
| --------------------------------- | ---------------------------------------------------------------- |
| `request_id`                      | Identifier for this delivery.                                    |
| `correlation_id`                  | Identifier you can use to correlate related deliveries and logs. |
| `timestamp`                       | ISO timestamp when the payload was created.                      |
| `tenant_id`, `tenantID`           | Tenant identifier. Both keys carry the same value.               |
| `application_id`, `applicationID` | Application identifier. Both keys carry the same value.          |
| `data.status`                     | Lifecycle stage represented by this snapshot.                    |
| `error`                           | Always `null` for successful lifecycle snapshots.                |

Use whichever naming style fits your system. For example, JavaScript systems may prefer `tenantID`, while data warehouses may prefer `tenant_id`.

## Lifecycle Stages

Hybrid Experience can send the following V2 lifecycle stages.

| Status               | Meaning                                                                                                                     |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `STARTED`            | The borrower journey has been created and offer discovery has started.                                                      |
| `NOT_PROFILED`       | Borrower profiling did not complete. Treat this as a pre-offer terminal snapshot, not as profiled, offered, or not-offered. |
| `PROFILED`           | Borrower profile details are available for offer discovery.                                                                 |
| `ACCOUNT_AGGREGATOR` | An Account Aggregator URL is available for the borrower.                                                                    |
| `OFFERED`            | One or more offers are available. This can happen once for non-AA offers and again later for AA-backed offers.              |
| `NOT_OFFERED`        | Offer discovery completed without an eligible offer for the relevant path.                                                  |
| `OFFER_ACCEPTED`     | The borrower accepted an offer.                                                                                             |
| `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.                                                          |

## Hybrid Offer Paths

A Hybrid journey can receive both non-AA and AA updates.

Common paths include:

* non-AA offers arrive, then the borrower accepts a non-AA offer
* non-AA offers arrive, then an Account Aggregator URL becomes available, then AA-backed offers arrive
* no non-AA offers arrive, then an Account Aggregator URL becomes available, then AA-backed offers arrive
* no eligible offers arrive after all relevant offer discovery paths complete

Because AA-backed offers can arrive after earlier non-AA offers, your receiver should treat `OFFERED` as additive. A later `OFFERED` payload can contain new offers for the same journey.

## Stage Contents

V2 includes only the sections that make sense for the lifecycle stage being delivered.

| Status               | `journey` | `borrower` | `lenders`            | `offers`                            | `accountAggregator` / `account_aggregator` | `installments` |
| -------------------- | --------- | ---------- | -------------------- | ----------------------------------- | ------------------------------------------ | -------------- |
| `STARTED`            | Minimal   | No         | No                   | No                                  | No                                         | No             |
| `NOT_PROFILED`       | Minimal   | No         | No                   | No                                  | No                                         | No             |
| `PROFILED`           | Profiled  | Yes        | No                   | No                                  | No                                         | No             |
| `ACCOUNT_AGGREGATOR` | Full      | Yes        | Yes                  | Current visible offers when present | Yes                                        | No             |
| `OFFERED`            | Full      | Yes        | Yes                  | Yes                                 | No                                         | No             |
| `NOT_OFFERED`        | Full      | Yes        | Lender metadata only | No                                  | No                                         | No             |
| `OFFER_ACCEPTED`     | Full      | Yes        | Yes                  | Yes                                 | No                                         | No             |
| `LENDER_REDIRECTED`  | Full      | Yes        | Yes                  | Yes                                 | No                                         | No             |
| `REJECTED`           | Full      | Yes        | Yes                  | Known offers when present           | No                                         | No             |
| `DISBURSED`          | Full      | Yes        | Yes                  | Yes                                 | No                                         | Yes            |

Missing sections are intentionally absent for that stage. For example, if a `PROFILED` payload does not include `lenders`, it does not mean lender data was deleted. It means lender data is not part of the `PROFILED` snapshot.

## ONDC Transaction IDs

ONDC transaction IDs appear in two shapes:

* `data.journey.ondcTransactionIds` is the lender-wise map keyed by ONDC BPP ID. Use it to reconcile available lender tracks once lender discovery has produced them.
* `data.journey.ondcTransactionID` is the selected-track transaction ID. It stays `null` until an offer is locked to the journey.
* `data.lenders[*].offers[*].ondcTransactionID` 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 discovery-time ONDC IDs. Before offer lock, use `ondcTransactionIds` for lender-wise reconciliation and expect the singular fields to be `null`.

## `STARTED` Shape

`STARTED` is intentionally small. It confirms that the journey exists.

```json
{
  "request_id": "event-id",
  "correlation_id": "event-id",
  "timestamp": "2026-05-12T10:00:00.000Z",
  "tenant_id": "tenant-id",
  "application_id": "application-id",
  "tenantID": "tenant-id",
  "applicationID": "application-id",
  "data": {
    "status": "STARTED",
    "journey": {
      "id": "journey-id",
      "status": "STARTED",
      "type": "PERSONAL_LOAN",
      "created": "2026-05-12T10:00:00.000Z",
      "updated": "2026-05-12T10:00:00.000Z",
      "decisionStatus": null,
      "ondcTransactionID": null,
      "ondcTransactionIds": {}
    }
  },
  "error": null
}
```

## `NOT_PROFILED` Shape

`NOT_PROFILED` is intentionally small. It confirms that profiling did not complete and no offer discovery result is implied.

```json
{
  "request_id": "event-id",
  "correlation_id": "event-id",
  "timestamp": "2026-05-12T10:01:00.000Z",
  "tenant_id": "tenant-id",
  "application_id": "application-id",
  "tenantID": "tenant-id",
  "applicationID": "application-id",
  "data": {
    "status": "NOT_PROFILED",
    "journey": {
      "id": "journey-id",
      "status": "NOT_PROFILED",
      "type": "PERSONAL_LOAN",
      "created": "2026-05-12T10:00:00.000Z",
      "updated": "2026-05-12T10:01:00.000Z",
      "decisionStatus": null,
      "ondcTransactionID": null,
      "ondcTransactionIds": {}
    }
  },
  "error": null
}
```

## `PROFILED` Shape

`PROFILED` adds borrower profile information.

```json
{
  "request_id": "event-id",
  "correlation_id": "event-id",
  "timestamp": "2026-05-12T10:01:00.000Z",
  "tenant_id": "tenant-id",
  "application_id": "application-id",
  "tenantID": "tenant-id",
  "applicationID": "application-id",
  "data": {
    "status": "PROFILED",
    "journey": {
      "id": "journey-id",
      "status": "PROFILED",
      "type": "PERSONAL_LOAN",
      "endUse": "education",
      "bureauConsent": true,
      "created": "2026-05-12T10:00:00.000Z",
      "updated": "2026-05-12T10:01:00.000Z",
      "decisionStatus": null,
      "ondcTransactionID": null,
      "ondcTransactionIds": {}
    },
    "borrower": {
      "id": "borrower-id",
      "provider_id": "partner-borrower-id",
      "providerID": "partner-borrower-id",
      "profile": {
        "personal": {
          "pan": "ABCDE1234F",
          "panName": "Jane Doe",
          "dob": "1990-01-01",
          "gender": "female",
          "personalemail": "jane@example.com",
          "contactNumber": "+919999999999"
        },
        "work": {
          "employmentType": "salaried",
          "officialemail": "jane@work.example",
          "income": 550000,
          "companyName": "Acme Corp",
          "udyamNumber": null
        },
        "address": {
          "addressL1": "Line 1",
          "addressL2": null,
          "city": "Mumbai",
          "state": "MH",
          "pincode": "400001"
        }
      }
    }
  },
  "error": null
}
```

## `ACCOUNT_AGGREGATOR` Shape

`ACCOUNT_AGGREGATOR` is sent when an Account Aggregator URL is available for the borrower.

This stage includes both naming styles:

* `accountAggregator.expiresAt`
* `account_aggregator.expires_at`

Both objects carry the same values.

```json
{
  "request_id": "event-id",
  "correlation_id": "event-id",
  "timestamp": "2026-05-12T10:10:00.000Z",
  "tenant_id": "tenant-id",
  "application_id": "application-id",
  "tenantID": "tenant-id",
  "applicationID": "application-id",
  "data": {
    "status": "ACCOUNT_AGGREGATOR",
    "accountAggregator": {
      "url": "https://credit.aarthiklabs.com/lab/account-aggregator?token=...",
      "expiresAt": "2026-05-12T10:40:00.000Z",
      "lenders": ["AA Bank"]
    },
    "account_aggregator": {
      "url": "https://credit.aarthiklabs.com/lab/account-aggregator?token=...",
      "expires_at": "2026-05-12T10:40:00.000Z",
      "lenders": ["AA Bank"]
    },
    "journey": {
      "id": "journey-id",
      "status": "ACCOUNT_AGGREGATOR",
      "type": "PERSONAL_LOAN",
      "endUse": "education",
      "bureauConsent": true,
      "created": "2026-05-12T10:00:00.000Z",
      "updated": "2026-05-12T10:10:00.000Z",
      "decisionStatus": null,
      "kycStatus": "PENDING",
      "kycAttempts": 1,
      "emandateStatus": null,
      "emandateAttempts": null,
      "esignStatus": null,
      "esignAttempts": null,
      "changeInOffer": false,
      "borrowerPreferredAmount": 200000,
      "borrowerPreferredTenure": 12,
      "ondcTransactionID": null,
      "ondcTransactionIds": {
        "bpp.id.of.lender": "ondc-tx-id"
      }
    },
    "borrower": {
      "id": "borrower-id",
      "provider_id": "partner-borrower-id",
      "providerID": "partner-borrower-id",
      "profile": {
        "personal": {
          "pan": "ABCDE1234F",
          "panName": "Jane Doe",
          "dob": "1990-01-01",
          "gender": "female",
          "personalemail": "jane@example.com",
          "contactNumber": "+919999999999"
        },
        "work": {
          "employmentType": "salaried",
          "officialemail": "jane@work.example",
          "income": 550000,
          "companyName": "Acme Corp",
          "udyamNumber": null
        },
        "address": {
          "addressL1": "Line 1",
          "addressL2": null,
          "city": "Mumbai",
          "state": "MH",
          "pincode": "400001"
        }
      }
    },
    "lenders": {
      "AA Bank": {
        "id": "bpp.id.of.lender",
        "minimumLoanAmount": "50000.00",
        "maximumLoanAmount": "5000000.00",
        "configuration": {
          "mode": "ONLINE",
          "redirectionType": "SINGLE",
          "loanAmountAdjustmentAllowed": true
        },
        "gro": {
          "name": null,
          "email": null,
          "contactNumber": null,
          "designation": null,
          "address": null
        },
        "customerSupport": {
          "link": null,
          "contactNumber": null,
          "email": null
        },
        "offers": []
      }
    }
  },
  "error": null
}
```

Only `ACCOUNT_AGGREGATOR` includes `accountAggregator` and `account_aggregator`.

## `OFFERED` Shape

`OFFERED` includes lenders and offers that are available for the borrower.

```json
{
  "request_id": "event-id",
  "correlation_id": "event-id",
  "timestamp": "2026-05-12T10:20:00.000Z",
  "tenant_id": "tenant-id",
  "application_id": "application-id",
  "tenantID": "tenant-id",
  "applicationID": "application-id",
  "data": {
    "status": "OFFERED",
    "lenders": {
      "Non-AA Bank": {
        "id": "bpp.id.of.lender",
        "minimumLoanAmount": "50000.00",
        "maximumLoanAmount": "5000000.00",
        "configuration": {
          "mode": "ONLINE",
          "redirectionType": "SINGLE",
          "loanAmountAdjustmentAllowed": true
        },
        "gro": {
          "name": "Name",
          "email": "gro@example.com",
          "contactNumber": "+919999999999",
          "designation": "Manager",
          "address": "Address"
        },
        "customerSupport": {
          "link": "https://example.com/support",
          "contactNumber": "+919999999999",
          "email": "support@example.com"
        },
        "offers": [
          {
            "offerID": "offer-id",
            "consentHandleAvailable": false,
            "installmentAmount": null,
            "ltvRatio": null,
            "status": "OFFER_RECEIVED",
            "periods": {
              "offerTerm": "12 Months",
              "coolOffPeriod": "3 Days"
            },
            "interest": {
              "rate": "12.50",
              "type": "Fixed"
            },
            "amounts": {
              "principal": "460000",
              "totalInterestPayable": "15000",
              "netDisbursedAmount": "480000"
            },
            "annualPercentageRate": "14.10",
            "ondcTransactionID": null,
            "accepted": false,
            "expired": false,
            "documents": {
              "termsAndConditionsLink": "https://example.com/terms",
              "keyFactsStatementLink": "https://example.com/kfs"
            },
            "chargesAndFees": {
              "applicationFees": "0",
              "foreclosureFees": "0",
              "interestRateConversionCharges": "0",
              "delayPenaltyFees": "0",
              "otherPenaltyFees": "0",
              "processingFees": "999",
              "otherUpfrontCharges": "0",
              "insuranceCharges": "0",
              "otherCharges": "0"
            },
            "numberOfInstallments": 12
          }
        ]
      }
    },
    "journey": {
      "id": "journey-id",
      "status": "OFFERED",
      "type": "PERSONAL_LOAN",
      "endUse": "education",
      "bureauConsent": true,
      "created": "2026-05-12T10:00:00.000Z",
      "updated": "2026-05-12T10:20:00.000Z",
      "decisionStatus": null,
      "kycStatus": "PENDING",
      "kycAttempts": 1,
      "emandateStatus": null,
      "emandateAttempts": null,
      "esignStatus": null,
      "esignAttempts": null,
      "changeInOffer": false,
      "borrowerPreferredAmount": 200000,
      "borrowerPreferredTenure": 12,
      "ondcTransactionID": null,
      "ondcTransactionIds": {
        "bpp.id.of.lender": "ondc-tx-id"
      }
    },
    "borrower": {
      "id": "borrower-id",
      "provider_id": "partner-borrower-id",
      "providerID": "partner-borrower-id",
      "profile": {
        "personal": {
          "pan": "ABCDE1234F",
          "panName": "Jane Doe",
          "dob": "1990-01-01",
          "gender": "female",
          "personalemail": "jane@example.com",
          "contactNumber": "+919999999999"
        },
        "work": {
          "employmentType": "salaried",
          "officialemail": "jane@work.example",
          "income": 550000,
          "companyName": "Acme Corp",
          "udyamNumber": null
        },
        "address": {
          "addressL1": "Line 1",
          "addressL2": null,
          "city": "Mumbai",
          "state": "MH",
          "pincode": "400001"
        }
      }
    }
  },
  "error": null
}
```

## `NOT_OFFERED` Shape

`NOT_OFFERED` includes journey, borrower, and lender metadata. It does not include an `offers` array.

```json
{
  "request_id": "event-id",
  "correlation_id": "event-id",
  "timestamp": "2026-05-12T10:20:00.000Z",
  "tenant_id": "tenant-id",
  "application_id": "application-id",
  "tenantID": "tenant-id",
  "applicationID": "application-id",
  "data": {
    "status": "NOT_OFFERED",
    "lenders": {
      "Non-AA Bank": {
        "id": "bpp.id.of.lender",
        "minimumLoanAmount": "50000.00",
        "maximumLoanAmount": "5000000.00",
        "configuration": {
          "mode": "ONLINE",
          "redirectionType": "SINGLE",
          "loanAmountAdjustmentAllowed": true
        },
        "gro": {
          "name": "Name",
          "email": "gro@example.com",
          "contactNumber": "+919999999999",
          "designation": "Manager",
          "address": "Address"
        },
        "customerSupport": {
          "link": "https://example.com/support",
          "contactNumber": "+919999999999",
          "email": "support@example.com"
        }
      }
    },
    "journey": {
      "id": "journey-id",
      "status": "NOT_OFFERED",
      "type": "PERSONAL_LOAN",
      "endUse": "education",
      "bureauConsent": true,
      "created": "2026-05-12T10:00:00.000Z",
      "updated": "2026-05-12T10:20:00.000Z",
      "decisionStatus": null,
      "kycStatus": "PENDING",
      "kycAttempts": 1,
      "emandateStatus": null,
      "emandateAttempts": null,
      "esignStatus": null,
      "esignAttempts": null,
      "changeInOffer": false,
      "borrowerPreferredAmount": 200000,
      "borrowerPreferredTenure": 12,
      "ondcTransactionID": null,
      "ondcTransactionIds": {
        "bpp.id.of.lender": "ondc-tx-id"
      }
    },
    "borrower": {
      "id": "borrower-id",
      "provider_id": "partner-borrower-id",
      "providerID": "partner-borrower-id",
      "profile": {
        "personal": {
          "pan": "ABCDE1234F",
          "panName": "Jane Doe",
          "dob": "1990-01-01",
          "gender": "female",
          "personalemail": "jane@example.com",
          "contactNumber": "+919999999999"
        },
        "work": {
          "employmentType": "salaried",
          "officialemail": "jane@work.example",
          "income": 550000,
          "companyName": "Acme Corp",
          "udyamNumber": null
        },
        "address": {
          "addressL1": "Line 1",
          "addressL2": null,
          "city": "Mumbai",
          "state": "MH",
          "pincode": "400001"
        }
      }
    }
  },
  "error": null
}
```

## `OFFER_ACCEPTED`, `LENDER_REDIRECTED`, And `REJECTED` Shapes

These stages use the same broad structure as `OFFERED`:

* `journey`
* `borrower`
* `lenders`
* `offers`, when offers are known for the journey

The main changes are the stage in `data.status` and `journey.status`, plus the latest offer flags and verification fields.

For example, an accepted-offer snapshot marks the accepted offer with `accepted: true`.

```json
{
  "data": {
    "status": "OFFER_ACCEPTED",
    "lenders": {
      "Selected Bank": {
        "id": "bpp.id.of.lender",
        "minimumLoanAmount": "50000.00",
        "maximumLoanAmount": "5000000.00",
        "configuration": {
          "mode": "ONLINE",
          "redirectionType": "SINGLE",
          "loanAmountAdjustmentAllowed": true
        },
        "gro": {
          "name": "Name",
          "email": "gro@example.com",
          "contactNumber": "+919999999999",
          "designation": "Manager",
          "address": "Address"
        },
        "customerSupport": {
          "link": "https://example.com/support",
          "contactNumber": "+919999999999",
          "email": "support@example.com"
        },
        "offers": [
          {
            "offerID": "offer-id",
            "consentHandleAvailable": false,
            "installmentAmount": null,
            "ltvRatio": null,
            "status": "OFFER_RECEIVED",
            "periods": {
              "offerTerm": "12 Months",
              "coolOffPeriod": "3 Days"
            },
            "interest": {
              "rate": "12.50",
              "type": "Fixed"
            },
            "amounts": {
              "principal": "460000",
              "totalInterestPayable": "15000",
              "netDisbursedAmount": "480000"
            },
            "annualPercentageRate": "14.10",
            "ondcTransactionID": "ondc-tx-id",
            "accepted": true,
            "expired": false,
            "documents": {
              "termsAndConditionsLink": "https://example.com/terms",
              "keyFactsStatementLink": "https://example.com/kfs"
            },
            "chargesAndFees": {
              "applicationFees": "0",
              "foreclosureFees": "0",
              "interestRateConversionCharges": "0",
              "delayPenaltyFees": "0",
              "otherPenaltyFees": "0",
              "processingFees": "999",
              "otherUpfrontCharges": "0",
              "insuranceCharges": "0",
              "otherCharges": "0"
            },
            "numberOfInstallments": 12
          }
        ]
      }
    },
    "journey": {
      "id": "journey-id",
      "status": "OFFER_ACCEPTED",
      "type": "PERSONAL_LOAN",
      "endUse": "education",
      "bureauConsent": true,
      "created": "2026-05-12T10:00:00.000Z",
      "updated": "2026-05-12T10:25:00.000Z",
      "decisionStatus": null,
      "kycStatus": "PENDING",
      "kycAttempts": 1,
      "emandateStatus": null,
      "emandateAttempts": null,
      "esignStatus": null,
      "esignAttempts": null,
      "changeInOffer": false,
      "borrowerPreferredAmount": 200000,
      "borrowerPreferredTenure": 12,
      "ondcTransactionID": "ondc-tx-id",
      "ondcTransactionIds": {
        "bpp.id.of.lender": "ondc-tx-id"
      }
    },
    "borrower": {
      "id": "borrower-id",
      "provider_id": "partner-borrower-id",
      "providerID": "partner-borrower-id",
      "profile": {}
    }
  },
  "error": null
}
```

For `LENDER_REDIRECTED`, use the same shape with `data.status: "LENDER_REDIRECTED"` and `journey.status: "LENDER_REDIRECTED"`.

For `REJECTED`, use the same shape with `data.status: "REJECTED"` and `journey.status: "REJECTED"`. Known lenders and offers may still be present to help reconciliation and support workflows.

## `DISBURSED` Shape

`DISBURSED` is the only V2 stage that includes `installments`.

```json
{
  "request_id": "event-id",
  "correlation_id": "event-id",
  "timestamp": "2026-05-12T10:45:00.000Z",
  "tenant_id": "tenant-id",
  "application_id": "application-id",
  "tenantID": "tenant-id",
  "applicationID": "application-id",
  "data": {
    "status": "DISBURSED",
    "lenders": {
      "Selected Bank": {
        "id": "bpp.id.of.lender",
        "minimumLoanAmount": "50000.00",
        "maximumLoanAmount": "5000000.00",
        "configuration": {
          "mode": "ONLINE",
          "redirectionType": "SINGLE",
          "loanAmountAdjustmentAllowed": true
        },
        "gro": {
          "name": "Name",
          "email": "gro@example.com",
          "contactNumber": "+919999999999",
          "designation": "Manager",
          "address": "Address"
        },
        "customerSupport": {
          "link": "https://example.com/support",
          "contactNumber": "+919999999999",
          "email": "support@example.com"
        },
        "offers": [
          {
            "offerID": "offer-id",
            "consentHandleAvailable": false,
            "installmentAmount": null,
            "ltvRatio": null,
            "status": "OFFER_RECEIVED",
            "periods": {
              "offerTerm": "12 Months",
              "coolOffPeriod": "3 Days"
            },
            "interest": {
              "rate": "12.50",
              "type": "Fixed"
            },
            "amounts": {
              "principal": "460000",
              "totalInterestPayable": "15000",
              "netDisbursedAmount": "480000"
            },
            "annualPercentageRate": "14.10",
            "ondcTransactionID": "ondc-tx-id",
            "accepted": true,
            "expired": false,
            "documents": {
              "termsAndConditionsLink": "https://example.com/terms",
              "keyFactsStatementLink": "https://example.com/kfs"
            },
            "chargesAndFees": {
              "applicationFees": "0",
              "foreclosureFees": "0",
              "interestRateConversionCharges": "0",
              "delayPenaltyFees": "0",
              "otherPenaltyFees": "0",
              "processingFees": "999",
              "otherUpfrontCharges": "0",
              "insuranceCharges": "0",
              "otherCharges": "0"
            },
            "numberOfInstallments": 12,
            "installments": [
              {
                "serial_number": 1,
                "type": "INSTALLMENT",
                "amount": "11917",
                "status": "NOT-DUE",
                "installment_due_date": "2026-06-09T00:00:00.000Z"
              }
            ]
          }
        ]
      }
    },
    "journey": {
      "id": "journey-id",
      "status": "DISBURSED",
      "type": "PERSONAL_LOAN",
      "endUse": "education",
      "bureauConsent": true,
      "created": "2026-05-12T10:00:00.000Z",
      "updated": "2026-05-12T10:45:00.000Z",
      "decisionStatus": null,
      "kycStatus": "SUCCESS",
      "kycAttempts": 1,
      "emandateStatus": "SUCCESS",
      "emandateAttempts": 1,
      "esignStatus": "SUCCESS",
      "esignAttempts": 1,
      "changeInOffer": false,
      "borrowerPreferredAmount": 200000,
      "borrowerPreferredTenure": 12,
      "ondcTransactionID": "ondc-tx-id",
      "ondcTransactionIds": {
        "bpp.id.of.lender": "ondc-tx-id"
      }
    },
    "borrower": {
      "id": "borrower-id",
      "provider_id": "partner-borrower-id",
      "providerID": "partner-borrower-id",
      "profile": {}
    }
  },
  "error": null
}
```

## Object Reference

### `journey`

`journey` identifies the borrower journey and the latest lifecycle state. `journey.id` is present in every lifecycle stage and matches `data.journey.id` in Hybrid Start/Hosted API responses and `data.journeyID` in Hybrid Offers API responses.

| Field                                              | Type           | Notes                                                                 |
| -------------------------------------------------- | -------------- | --------------------------------------------------------------------- |
| `id`                                               | string         | Aarthik Labs journey ID.                                              |
| `status`                                           | string         | Same lifecycle stage as `data.status`.                                |
| `type`                                             | string         | Product type, for example `PERSONAL_LOAN`.                            |
| `endUse`                                           | string         | Present after `STARTED` when available.                               |
| `bureauConsent`                                    | boolean        | Present after `STARTED` when available.                               |
| `created`                                          | string         | ISO timestamp.                                                        |
| `updated`                                          | string         | ISO timestamp.                                                        |
| `decisionStatus`                                   | string or null | Latest decision status, if available.                                 |
| `kycStatus`, `emandateStatus`, `esignStatus`       | string or null | Verification statuses, present in full journey snapshots.             |
| `kycAttempts`, `emandateAttempts`, `esignAttempts` | number or null | Attempt counts, present in full journey snapshots.                    |
| `changeInOffer`                                    | boolean        | Whether the selected offer changed after borrower action.             |
| `borrowerPreferredAmount`                          | number or null | Borrower's preferred amount when available.                           |
| `borrowerPreferredTenure`                          | number or null | Borrower's preferred tenure when available.                           |
| `ondcTransactionID`                                | string or null | Selected locked-track transaction ID. It is `null` before offer lock. |
| `ondcTransactionIds`                               | object         | Lender-wise transaction ID map keyed by ONDC BPP ID.                  |

### `borrower`

`borrower` is present from `PROFILED` onward.

| Field         | Type   | Notes                                                               |
| ------------- | ------ | ------------------------------------------------------------------- |
| `id`          | string | Aarthik Labs borrower ID.                                           |
| `provider_id` | string | Borrower ID from your system.                                       |
| `providerID`  | string | Same value as `provider_id`, provided for naming-style convenience. |
| `profile`     | object | Borrower profile submitted for the journey.                         |

### `accountAggregator` And `account_aggregator`

These objects appear only in `ACCOUNT_AGGREGATOR` snapshots.

| Field                      | Type           | Notes                                                                      |
| -------------------------- | -------------- | -------------------------------------------------------------------------- |
| `url`                      | string or null | URL that the borrower should open for the Account Aggregator consent flow. |
| `expiresAt` / `expires_at` | string or null | ISO expiry timestamp for the URL.                                          |
| `lenders`                  | string\[]      | Lenders associated with the Account Aggregator flow.                       |

### `lenders`

`lenders` is an object keyed by borrower-visible lender name.

If two lenders have the same visible name, both entries are included with unique keys.

| Field                                       | Type           | Notes                                                         |
| ------------------------------------------- | -------------- | ------------------------------------------------------------- |
| `id`                                        | string         | Lender identifier.                                            |
| `minimumLoanAmount`                         | string or null | Minimum loan amount supported by the lender for this journey. |
| `maximumLoanAmount`                         | string or null | Maximum loan amount supported by the lender for this journey. |
| `configuration.mode`                        | string or null | Lender mode, for example `ONLINE`.                            |
| `configuration.redirectionType`             | string or null | Redirection type when applicable.                             |
| `configuration.loanAmountAdjustmentAllowed` | boolean        | Whether borrower amount adjustment is allowed.                |
| `gro`                                       | object         | Grievance redressal contact information when available.       |
| `customerSupport`                           | object         | Customer support contact information when available.          |
| `offers`                                    | array          | Present only for stages that include offers.                  |

### `offers`

`offers` is present for stages where offer details are available.

| Field                              | Type            | Notes                                                                                        |
| ---------------------------------- | --------------- | -------------------------------------------------------------------------------------------- |
| `offerID`                          | string or null  | Aarthik Labs offer ID.                                                                       |
| `consentHandleAvailable`           | boolean or null | Whether the offer is associated with an Account Aggregator consent handle.                   |
| `installmentAmount`                | string or null  | Installment amount when available.                                                           |
| `ltvRatio`                         | string or null  | Loan-to-value ratio when available.                                                          |
| `status`                           | string          | Offer status.                                                                                |
| `periods.offerTerm`                | string or null  | Offer term.                                                                                  |
| `periods.coolOffPeriod`            | string or null  | Cool-off period.                                                                             |
| `interest.rate`                    | string or null  | Interest rate.                                                                               |
| `interest.type`                    | string          | Interest type.                                                                               |
| `amounts.principal`                | string or null  | Principal amount.                                                                            |
| `amounts.totalInterestPayable`     | string or null  | Total interest payable.                                                                      |
| `amounts.netDisbursedAmount`       | string or null  | Net disbursed amount.                                                                        |
| `annualPercentageRate`             | string or null  | Annual percentage rate.                                                                      |
| `ondcTransactionID`                | string or null  | Selected locked-offer transaction ID. It is `null` before offer lock and for sibling offers. |
| `accepted`                         | boolean         | Whether this offer has been accepted.                                                        |
| `expired`                          | boolean         | Whether this offer has expired.                                                              |
| `documents.termsAndConditionsLink` | string or null  | Terms and conditions document link.                                                          |
| `documents.keyFactsStatementLink`  | string or null  | Key Facts Statement document link.                                                           |
| `chargesAndFees`                   | object          | Fee and charge fields, each as string or null.                                               |
| `numberOfInstallments`             | number or null  | Number of installments.                                                                      |
| `installments`                     | array           | Present only in `DISBURSED` snapshots.                                                       |

### `installments`

`installments` appears only in `DISBURSED` snapshots.

| Field                  | Type           | Notes                        |
| ---------------------- | -------------- | ---------------------------- |
| `serial_number`        | number or null | Installment sequence number. |
| `type`                 | string         | Usually `INSTALLMENT`.       |
| `amount`               | string         | Installment amount.          |
| `status`               | string         | Installment status.          |
| `installment_due_date` | string         | ISO due date.                |

## Recommended Receiver Logic

1. Verify the signature before trusting the payload.
2. Deduplicate using `X-AL-Event-ID`.
3. Read `data.status`.
4. Upsert the latest `journey`, `borrower`, `lenders`, and `offers` details into your system when those sections are present.
5. Treat `ACCOUNT_AGGREGATOR` as an action signal to make the AA URL available to the borrower.
6. Treat `OFFERED` as additive. A journey can receive a later `OFFERED` snapshot when AA-backed offers arrive.
7. Treat missing optional sections as intentionally absent for that stage.
8. Ignore unknown fields so your receiver remains forward-compatible.

## Polling And Web-Hooks

Hybrid offer polling and web-hooks complement each other.

* Polling keeps your borrower-facing UI responsive while the borrower is active.
* Web-hooks give your back-end durable lifecycle updates even if the borrower closes the screen, completes Account Aggregator later, or moves into lender-managed steps.
* When an Account Aggregator URL becomes available, V2 can send an `ACCOUNT_AGGREGATOR` web-hook so your back-end can reconcile the same milestone.

## Enabling V2

Web-Hooks (V2) is enabled by Aarthik Labs for a specific application. Contact Aarthik Labs to enable it for your integration.

Until V2 is enabled, your application continues receiving the existing web-hook payload.