---
title: Consent Start
slug: docs.s8933.in
description: Find comprehensive documentation and support for Signzy's platform and APIs, which revolutionize onboarding processes for financial institutions. Explore categories like API marketplace, no code platform, video KYC, and more. With over 240 global financia
createdAt: 2023-03-21T10:12:02.000Z
---

***

## Introduction

About the Signzy Account Aggregator (AAA) product.
The Account Aggregator (AA) framework is an RBI-regulated, consent-driven system that lets a customer securely share their financial data from banks and other institutions (FIPs — Financial Information Providers) with a business that needs it (an FIU — Financial Information User), through a licensed Account Aggregator. No data moves without the customer's explicit, purpose-bound, time-bound digital consent.

Signzy's TSP solution is the FIU-side integration that hides all of this complexity behind a small set of REST APIs. It manages Sahamati onboarding, consent creation and signing, the AA webview/redirect handshake, encrypted FI data pulls from each bank, decryption, and normalization.

**The intended integration is one API call plus a webhook.** You call start once to open a journey, redirect the customer, and then receive every outcome — consent approved, consent dead, journey finished, data ready, data failed — as callback events on your own endpoint. The status and data APIs exist for polling and reconciliation; a callback-driven integration does not need to call them on the happy path.

Where start fits. start is the entry point of a consent journey. You call it with the customer's identifier (mobile) and one or more of your configured consent templates; it creates the consent(s) and returns a redirectUrl to which you send the customer. The typical journey is:

1. start → create the consent(s), get journeyId + redirectUrl.
2. Redirect the customer to redirectUrl — the AA consent screen (if you passed fipIds) or Signzy's bank-selection screen (if you didn't).
3. Receive callback events on your callbackUrl as the journey progresses — see [Callback Events](#) below.
4. Optional fallback: poll status (with journeyId) and read data (per consentHandle) if you cannot rely on webhooks.

## How to call the API

Send a POST request. Every request must carry your Signzy API key in the Authorization header and Content-Type: application/json.

Authorization:&#x20;
Content-Type: application/json

## API Input Guidelines

You need to pass the following information:

- mobile
- consentTemplateIds — one or more consent templates configured for you (one consent is created per template).
- Optionally fipIds (pre-selected banks), aaId, redirect URL(for return redirection to your screen), and a callbackUrl (to receive callback events).
- aaId mapping
  - **UAT**

:::Paragraph{indent="3"}
Finvu ---> "aaId": "[cookiejar-aa@finvu.in](mailto\:cookiejar-aa@finvu.in)",
Saafe ---> "aaId": "dashboard-aa-preprod",
NADL ---> "aaId": "AA00023403",
Anumati ---> "aaId": "Anumati-UAT",
CamsFinserv ---> "aaId": "AA00022222",
Onemoney ---> "aaId": "onemoney-aa"
:::

:::Paragraph{indent="3"}
**Production**
:::

:::Paragraph{indent="3"}
Finvu ---> "aaId": "cookiejaraalive\@finvu",
Saafe -->  "aaId": "saafe",
Onemoney ---> "aaId": "onemoney",
NADL --->  "aaId": "AA00023404",
Anumati --->    "aaId": "Anumati",
camsfinserv --->    "aaId": "AA00022277"
:::

:::Paragraph{indent="2"}
fipIds mapping
:::

:::Paragraph{indent="3"}
**UAT**
:::

::File{src="https://api.archbee.com/api/presign/w0YdjmC6Uxu0AJ6sPj9OT/fLQpVVBVhSawF1tyOZSIv_fip-uat.json" label="fip_uat.json" indent="3"}

::File{src="https://api.archbee.com/api/presign/w0YdjmC6Uxu0AJ6sPj9OT/dNW3mceJiG49ycpPL3E92_fip-prod.json" label="fip_prod.json" indent="3"}

- **OTP for AA login in uat**
  - Finvu: 111111
  - Saafe: 1234
- **OTP for Account Linking in AA uat screen (can click continue if already have accounts linked before)**
  - Millenium Bank: 367560

### Sample Curl

::::Tabs
:::Tab{title="Preproduction"}
```curl
curl --location 'https://api-preproduction.signzy.app/api/v3/aa-switch/consent/start' \
--header 'Content-Type: application/json' \
--header 'Authorization:<signzy-auth-key>' \
--data-raw '{
    "mobile": "<10-digit mobile number>",
    "consentTemplateIds": [
        "<your configured templateId (ONETIME, PERIODIC)>" 
    ],
    "includePdf": false,
    "callbackUrl": "<open post endpoint to recieve callback events>",
    "redirectSuccessUrl": "https://www.google.com/",
    "redirectFailureUrl": "https://www.google.com/",
    "fipIds": [
        "<fipId if banks are known before the journey>"
    ],
    "aaId": "if want to redirect to particular configured account aggregator"
}'
```
:::

:::Tab{title="Production"}
```curl
curl --location 'https://api.signzy.app/api/v3/aa-switch/consent/start' \
--header 'Content-Type: application/json' \
--header 'Authorization:<signzy-auth-key>' \
--data-raw '{
    "mobile": "<10-digit mobile number>",
    "consentTemplateIds": [
        "<your configured templateId (ONETIME, PERIODIC)>" 
    ],
    "includePdf":false,
    "callbackUrl": "<open post endpoint to recieve callback events>",
    "redirectSuccessUrl": "https://www.google.com/",
    "redirectFailureUrl": "https://www.google.com/",
    "fipIds": [
        "<fipId if banks are known before the journey>"
    ],
    "aaId": "if want to redirect to particular configured account aggregator"
}'
```
:::
::::

### Input Parameters

| **PARAMETER NAME** | **REQUIRED**/**OPTIONAL** | **DESCRIPTION**                                                                                                                                                        |
| ------------------ | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| mobile             | Required                  | Customer mobile number (E.164, e.g. 9876543210).                                                                                                                       |
| consentTemplateIds | Required                  | Array of one or more consent template ids configured for you. One consent is created per template.                                                                     |
| includePdf         | Optional                  | Optional boolean flag to get pdf of accounts data fetched from account aggregator.                                                                                     |
| redirectSuccessUrl | Optional                  | URL the user is returned to after a successful consent. Omit to use Signzy's own success screen.                                                                       |
| redirectFailureUrl | Optional                  | URL the user is returned to on failure / decline. Omit to use Signzy's own failure screen.                                                                             |
| callbackUrl        | Optional                  | Endpoint that receives this journey's callback events. By default only dataReady is sent — see[Callback Events](#).                                                    |
| aaId               | Optional                  | The Account Aggregator to use. If omitted, oneof your configured AA is used based on availability.                                                                     |
| fipIds             | Optional                  | Pre-selected banks. If omitted, the response redirectUrl is Signzy's bank-selection screen — the user picks banks there, which then resolves to the AA consent screen. |

### Sample Response

```json
{
    "journeyId": "fe2e8638-0348-47aa-b736-7f3fd1d2fb41",
    "consents": [
        {
            "consentHandle": "08785f8e-a8e6-41a6-8a0c-df5990d215c2",
            "consentTemplateId": "RICHMAN-ONETIME-single-01KVYTT71VB1NRQM8N92S46STP"
        },
        {
            "consentHandle": "94692ba5-8a37-4ad6-8b30-33757c542d7b",
            "consentTemplateId": "RICHMAN-PERIODIC-single-01KVZ0J1SS6H91A0QG65N3XVNT"
        }
    ],
    "redirectUrl": "https://snz.bz/SIGNZY/vfLEjB",
    "expiresAt": "2026-07-08T11:09:37.967Z"
}
```

### Response Parameter

| **PARAMETER NAME**            | **Type** | **DESCRIPTION**                                                                                                                                                       |
| ----------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| journeyId                     | String   | The journey id. Use it with the status call.                                                                                                                          |
| consents\[]                   | Array    | One\{ consentHandle, consentTemplateId } per consent template. Use each consentHandle with the data call.                                                             |
| consents\[].consentHandle     | String   | The consent handle for that template.                                                                                                                                 |
| consents\[].consentTemplateId | String   | The consent template the consent was created from.                                                                                                                    |
| redirectUrl                   | String   | Short URL to redirect the customer to: the AA consent screen (when fipIds passed) or Signzy's bank-selection screen (when omitted).                                   |
| expiresAt                     | String   | the redirect link expiry in ISO date format —\~30 minutes for the bank-selection screen (no fipIds), or the journey lifetime for the AA consent screen (with fipIds). |

## Callback Events

Once the customer leaves your app for the AA, everything that follows is asynchronous: the customer may approve, decline, walk away, or come back three weeks later and revoke. Callback events report each of those facts to your endpoint as it happens, so a single start call is the only API you need to integrate.

Events answer three independent questions, and all three can be true at once for the same journey:

| Category | Question it answers                       | Events                             |
| -------- | ----------------------------------------- | ---------------------------------- |
| journey  | Did the customer get through the AA flow? | journeyCompleted, journeyFailed    |
| consent  | What happened to each individual consent? | consentApproved, consentTerminated |
| data     | Did we actually get the financial data?   | dataReady, dataFailed              |

A multi-consent journey can finish successfully on the journey axis while one consent dies on the consent axis and another consent's data fails on the data axis.

Two guarantees worth building on:

1. **Exactly one journey event per journey.** SUCCESS produces journeyCompleted; every other terminal state produces journeyFailed carrying that state as reason. There is no journey outcome that produces zero or two journey events.
2. **Every consentHandle reaches a terminal consent event** — including the ones the customer silently ignored. You never have to infer meaning from silence.

### How callbacks are enabled

Two independent settings:

| Setting                    | Scope                             | How it is set                                                                          |
| -------------------------- | --------------------------------- | -------------------------------------------------------------------------------------- |
| Where events are delivered | Per journey                       | callbackUrl in the start request body. A standing default can be held on your account. |
| Which events are delivered | Your whole account (all journeys) | Configured by Signzy — contact your account team to change it.                         |

The callbackUrl passed to start always wins for that journey. The account default applies only when start omits it. The destination is resolved once, when the journey is created, so changing the default never redirects a journey already in flight. If neither is set, nothing is pushed at all.

Event selection works like this:

- **Default on every account: dataReady only.** This is exactly the behaviour that existed before the other events were introduced, so nothing changes for an existing integration.
- Selection is by **category** (journey / consent / data) or by **individual event name**, mixed freely.
- Your selection is pinned to explicit event names when it is configured. If a new event is added to a category later it will **not** start arriving and break your handler — new events are always an explicit opt-in.
- **dataReady can never be switched off.** It is included in every subscription regardless of what else you choose. Silencing optional events never silences the data.

### Event catalogue

| Category | Event             | Meaning                                                          | Availability                |
| -------- | ----------------- | ---------------------------------------------------------------- | --------------------------- |
| data     | dataReady         | A consent's financial data is fetched, parsed and ready          | Enabled by default          |
| data     | dataFailed        | The consent was ACTIVE but the fetch produced nothing at all     | On request — contact Signzy |
| consent  | consentApproved   | A consent went ACTIVE; its data may now be fetched               | On request — contact Signzy |
| consent  | consentTerminated | A consent is dead: rejected, revoked, expired, or never acted on | On request — contact Signzy |
| journey  | journeyCompleted  | The journey ended in SUCCESS — the customer finished the flow    | On request — contact Signzy |
| journey  | journeyFailed     | The journey ended in any non-SUCCESS terminal state              | On request — contact Signzy |

### The event envelope

Every event **except dataReady** is delivered in this envelope:

```json
{
  "eventId": "9f1c7a52-2b41-4d0e-9a77-6b0f2d5c8e31",
  "event": "journeyFailed",
  "category": "journey",
  "occurrenceTime": "2026-09-02T07:14:33.481Z",
  "eventData": {}
}
```

| FIELD          | TYPE            | DESCRIPTION                                                                                                                                        |
| -------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| eventId        | String          | UUID. Your de-duplication key — one fact has one id, and a re-delivery of that fact reuses it.                                                     |
| event          | String          | The catalogue value. Switch on this, and ignore event names you do not recognise.                                                                  |
| category       | String          | journey / consent / data. Derivable from event, sent so you can route without a lookup table.                                                      |
| occurrenceTime | DateTime String | ISO 8601 UTC, when the**fact happened** — not when it was pushed. An expiry noticed an hour late still reports the real expiry time.               |
| eventData      | Object          | Everything else, including the correlation keys journeyId, consentHandle and sessionId. Nothing but the four envelope fields ever sits outside it. |

:::BlockQuote
**dataReady is the one exception.** It keeps a flat body with no envelope, because it is the shape every existing integration already parses. Consequence: dataReady has no eventId, and is de-duplicated on (consentHandle, sessionId) instead.
:::

### Event Payloads

:::CodeblockTabs
dataReady

```json
{
  "event": "dataReady",
  "journeyId": "fe2e8638-0348-47aa-b736-7f3fd1d2fb41",
  "consentHandle": "08785f8e-a8e6-41a6-8a0c-df5990d215c2",
  "sessionId": "3d9b1c40-77aa-4e6f-8b21-5c0e9f4a1d77",
  "status": {
    "journeyStatus": "SUCCESS",
    "consentStatus": "ACTIVE",
    "fips": [
      {
        "fipId": "fip@finbank",
        "dataFetchStatus": "SUCCESS"
      }
    ],
    "failedAccounts": []
  },
  "accountCount": 2,
  "data": [
    {
      "fipId": "fip@finbank",
      "type": "deposit",
      "fiData": [
        {
          "linkedAccRef": "6de5df7d-5df8-4997-aaa3-42cbe77296d5",
          "maskedAccNumber": "FNRKXXXXXXX5876",
          "type": "deposit",
          "version": "1.1",
          "pdfUrl": "",
          "profile": { "holders": { "type": "SINGLE", "holder": {} } },
          "summary": {},
          "transactions": {}
        }
      ]
    }
  ],
  "fetchInProgress": false
}
```

dataFailed

```json
{
  "eventId": "0b7d2f18-9c34-4a51-b8e2-1f6a3d90c452",
  "event": "dataFailed",
  "category": "data",
  "occurrenceTime": "2026-09-02T07:14:33.481Z",
  "eventData": {
    "journeyId": "fe2e8638-0348-47aa-b736-7f3fd1d2fb41",
    "consentHandle": "08785f8e-a8e6-41a6-8a0c-df5990d215c2",
    "sessionId": "3d9b1c40-77aa-4e6f-8b21-5c0e9f4a1d77",
    "reason": "FETCH_FAILED",
    "message": "No data could be fetched for this consent; every FIP failed",
    "partialDataAvailable": false,
    "fips": [
      {
        "fipId": "BARB0KIMXXX",
        "dataFetchStatus": "FAILED",
        "accounts": [
          {
            "linkRefNumber": "9764373c-ecab-4ae1-982f-0bb5e95d2b12",
            "status": "DENIED"
          }
        ]
      }
    ]
  }
}
```

consentApproved

```json
{
  "eventId": "c41e6a09-5f7b-4c88-9d13-72b0ae845f6c",
  "event": "consentApproved",
  "category": "consent",
  "occurrenceTime": "2026-09-02T07:12:03.884Z",
  "eventData": {
    "journeyId": "fe2e8638-0348-47aa-b736-7f3fd1d2fb41",
    "consentHandle": "94692ba5-8a37-4ad6-8b30-33757c542d7b",
    "fetchType": "PERIODIC",
    "autoFetch": false,
    "message": "Consent approved by the customer at the Account Aggregator"
  }
}
```

consentTerminated

```json
{
  "eventId": "7a2f5d63-08c1-4b9e-a5d7-3e9c1b046f28",
  "event": "consentTerminated",
  "category": "consent",
  "occurrenceTime": "2026-09-14T11:42:56.003Z",
  "eventData": {
    "journeyId": "fe2e8638-0348-47aa-b736-7f3fd1d2fb41",
    "consentHandle": "94692ba5-8a37-4ad6-8b30-33757c542d7b",
    "consentStatus": "REVOKED",
    "reason": "REVOKED",
    "terminatedBy": "USER",
    "message": "User revoked an active consent; no further data fetch is permitted",
    "dataDelivered": true
  }
}
```

journeyCompleted

```json
{
  "eventId": "e8c04b71-6d2a-4f39-8c15-90ab7e2d63f4",
  "event": "journeyCompleted",
  "category": "journey",
  "occurrenceTime": "2026-09-02T07:12:04.010Z",
  "eventData": {
    "journeyId": "fe2e8638-0348-47aa-b736-7f3fd1d2fb41",
    "consents": [
      {
        "consentHandle": "08785f8e-a8e6-41a6-8a0c-df5990d215c2",
        "consentStatus": "ACTIVE"
      },
      {
        "consentHandle": "94692ba5-8a37-4ad6-8b30-33757c542d7b",
        "consentStatus": "PENDING"
      }
    ]
  }
}
```

journeyFailed

```json
{
  "eventId": "1d5b93e7-4a08-42cc-9f61-5c7e08d2ba39",
  "event": "journeyFailed",
  "category": "journey",
  "occurrenceTime": "2026-09-02T07:09:48.220Z",
  "eventData": {
    "journeyId": "fe2e8638-0348-47aa-b736-7f3fd1d2fb41",
    "consentHandles": [
      "08785f8e-a8e6-41a6-8a0c-df5990d215c2"
    ],
    "reason": "ACCOUNT_NOT_DISCOVERED",
    "errorCode": 7,
    "message": "No accounts were found for the customer at the selected FIP",
    "stage": "AA_DISCOVERY"
  }
}
```
:::

### dataReady parameters

Fires once per consent for automatic fetches, and once per session for fetches you request yourself. The status object and the data array are identical to what the data API returns — the full field-by-field breakdown of data\[] is on the Consent Data page.

| PARAMETER NAME           | TYPE           | DESCRIPTION                                                                                                                                                             |
| ------------------------ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| journeyId                | String         | The journey this consent belongs to.                                                                                                                                    |
| consentHandle            | String         | The consent whose data is ready.                                                                                                                                        |
| sessionId                | String         | The FI session whose data completed. For a fetch you requested, this equals the requestId you were handed, so you can correlate.                                        |
| status.journeyStatus     | String         | Status of the overall AA journey.                                                                                                                                       |
| status.consentStatus     | String         | Current status of the consent.                                                                                                                                          |
| status.fips\[]           | Array\<Object> | Per-FIP fetch outcome. dataFetchStatus is SUCCESS, DELIVERED, PARTIAL or FAILED — a FIP still in flight blocks the push entirely.                                       |
| status.failedAccounts\[] | Array\<Object> | Per-**account** failures for this session: \{ fipId, linkRefNumber, status } where status is DENIED or TIMEOUT (AA-reported) or FAILED. Always present, \[] when clean. |
| accountCount             | Integer        | Number of parsed accounts in this payload.                                                                                                                              |
| data\[]                  | Array\<Object> | Financial information grouped by FIP and account type. pdfUrl is populated only for includePdf consents (presigned, valid 24 hours).                                    |
| fetchInProgress          | Boolean        | Always false on a callback — the push only happens once the fetch has settled.                                                                                          |

### dataFailed parameters

Fires when the consent was ACTIVE but the fetch produced nothing at all. Exactly once per consent. A consent that was rejected, revoked or expired reports on the consent axis instead, so one death never produces two contradictory events.

:::BlockQuote
**dataFailed is never a partial statement.** If one FIP delivers and another fails, you get **dataReady** — the accounts that arrived are in data\[], and the failed side is in status.fips\[] and status.failedAccounts\[]. dataFailed fires only when there is nothing at all to hand you.
:::

| REASON           | MEANING                                                     |
| ---------------- | ----------------------------------------------------------- |
| FETCH\_FAILED    | **Every** FIP failed. partialDataAvailable is always false. |
| SESSION\_FAILED  | The AA reported the FI session as FAILED.                   |
| SESSION\_EXPIRED | The AA session expired before completing.                   |

| PARAMETER NAME       | TYPE           | DESCRIPTION                                                                                                                                                                                              |
| -------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| reason               | String         | FETCH\_FAILED, SESSION\_FAILED or SESSION\_EXPIRED.                                                                                                                                                      |
| message              | String         | Human-readable explanation.                                                                                                                                                                              |
| partialDataAvailable | Boolean        | Only meaningful on the two SESSION\_\* reasons. A dead AA session fails the consent regardless of what was already stored, so true means some accounts **are** readable via the data API — go read them. |
| fips\[]              | Array\<Object> | The FIPs that ended FAILED or PARTIAL, each with its failed accounts. accounts\[].status is DENIED or TIMEOUT (AA-reported) or FAILED.                                                                   |

### consentApproved parameters

Fires once per consent, when the AA reports it ACTIVE.

This is not redundant with dataReady. For an autoFetch: false consent **no data is fetched until you ask for it**, so this is the only event you will receive — without it, an integration with no polling loop would wait forever for data it has to request. Even with autoFetch: true it arrives well before the data does.

| PARAMETER NAME | TYPE    | DESCRIPTION                                                                                                 |
| -------------- | ------- | ----------------------------------------------------------------------------------------------------------- |
| consentHandle  | String  | The consent that was approved.                                                                              |
| fetchType      | String  | ONETIME or PERIODIC. A PERIODIC consent keeps serving re-fetches until it expires.                          |
| autoFetch      | Boolean | false means nothing further happens until you request a fetch. true means dataReady will follow on its own. |
| message        | String  | Human-readable explanation.                                                                                 |

### consentTerminated parameters

Fires when a consent can no longer be used. Each reason is a different business fact with different remediation — they are not synonyms.

| REASON           | TERMINATEDBY | MEANING                                                                                                  |
| ---------------- | ------------ | -------------------------------------------------------------------------------------------------------- |
| REJECTED         | USER         | The customer declined the consent request at the AA.                                                     |
| REVOKED          | USER         | The customer withdrew a previously ACTIVE consent —**may arrive days or weeks after approval**.          |
| EXPIRED          | SYSTEM       | The consent's own validity window ended. For a PERIODIC consent, the recurring fetch series has stopped. |
| NOT\_ACTED\_UPON | USER         | The customer returned from the AA without ever acting on this consent (multi-consent journeys).          |

| PARAMETER NAME | TYPE    | DESCRIPTION                                                                                                                     |
| -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------- |
| consentHandle  | String  | The consent that is now dead.                                                                                                   |
| consentStatus  | String  | The AA's last known status for it. PENDING on NOT\_ACTED\_UPON, because the AA never reported anything for that consent.        |
| reason         | String  | REJECTED, REVOKED, EXPIRED or NOT\_ACTED\_UPON.                                                                                 |
| terminatedBy   | String  | USER, SYSTEM or FIU — who ended it.                                                                                             |
| dataDelivered  | Boolean | Whether this consent ever delivered data to you. The difference between "nothing ever came" and "you hold data; stop fetching". |
| message        | String  | Human-readable explanation.                                                                                                     |

:::BlockQuote
**This event is not journey-bound.** A consent approved today can be revoked in two weeks, and the event fires then — long after the journey concluded. If you subscribe to consent events, your endpoint must stay available indefinitely.
:::

### journeyCompleted parameters

Fires once, when the journey reaches SUCCESS — the customer got through the AA flow.

| PARAMETER NAME            | TYPE           | DESCRIPTION                                                                          |
| ------------------------- | -------------- | ------------------------------------------------------------------------------------ |
| journeyId                 | String         | The journey that completed.                                                          |
| consents\[]               | Array\<Object> | The per-consent roster at the moment the journey concluded.                          |
| consents\[].consentHandle | String         | The consent handle.                                                                  |
| consents\[].consentStatus | String         | That consent's status when the journey closed — ACTIVE, PENDING, REJECTED and so on. |

:::BlockQuote
A journey is complete when the **customer finished the flow**, even if not every consent was approved — note the PENDING entry in the sample. Per-consent outcomes are the consent axis's job.
:::

### journeyFailed parameters

Fires once, when the journey reaches any non-SUCCESS terminal state. There is no separate expiry event: expiry is one more terminal state, reported here with reason EXPIRED.

| REASON                   | ERRORCODE      | STAGE             | MEANING                                                      |
| ------------------------ | -------------- | ----------------- | ------------------------------------------------------------ |
| USER\_REJECTED           | 1              | CONSENT\_APPROVAL | Consent explicitly rejected by the customer.                 |
| CONSENT\_NOT\_AVAILABLE  | 2              | CONSENT\_APPROVAL | Consent request not found at the AA.                         |
| INVALID\_REQUEST         | 3              | AA\_LOGIN         | Redirection request had invalid data.                        |
| AUTH\_FAILED             | 4              | AA\_LOGIN         | Customer authentication failed at the AA.                    |
| SESSION\_TIMEOUT         | 6              | AA\_LOGIN         | Customer did not act on the AA login page.                   |
| ACCOUNT\_NOT\_DISCOVERED | 7, 8, 9        | AA\_DISCOVERY     | FIP not found / FIP error / no accounts at any FIP.          |
| ACCOUNT\_NOT\_LINKED     | 10-16          | AA\_LINKING       | Accounts discovered but linking failed (timeout/cancel/OTP). |
| PARTIAL\_LINK\_FAILED    | 17, 18, 19     | AA\_LINKING       | Not all accounts linked successfully.                        |
| USER\_ABANDONED          | null           | last stage        | Browser returned from the AA with no result at all.          |
| EXPIRED                  | null           | last stage        | The journey timed out before the customer completed it.      |
| FAILED                   | 20 or unmapped | last stage        | Generic failure bucket / AA-reported unknown error.          |
| ERROR                    | null           | last stage        | AA returned a readable response with no status at all.       |

| PARAMETER NAME    | TYPE           | DESCRIPTION                                                                                                                             |
| ----------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| journeyId         | String         | The journey that failed.                                                                                                                |
| consentHandles\[] | Array\<String> | Every consent handle in the journey.                                                                                                    |
| reason            | String         | The terminal journey status, verbatim.                                                                                                  |
| errorCode         | Integer        | The raw AA web-redirection code, or null when the AA supplied none.                                                                     |
| stage             | String         | Where the journey died: BANK\_SELECTION, AA\_LOGIN, AA\_DISCOVERY, AA\_LINKING or CONSENT\_APPROVAL. null when it cannot be determined. |
| message           | String         | Human-readable explanation.                                                                                                             |

Two deadlines produce reason EXPIRED, and stage tells them apart:

| DEADLINE                                                  | STAGE              |
| --------------------------------------------------------- | ------------------ |
| Bank-selection link expired before the customer opened it | BANK\_SELECTION    |
| Journey still unfinished past its lifetime                | last stage reached |

Expiry is terminal: once expired, a late return from the AA can no longer complete the journey.

## Journey Scenarios

What you actually receive, end to end. Events within a journey are not guaranteed to arrive in the order listed — key on eventId, not on arrival order.

### 1. Single consent, approved, automatic fetch

The happy path for autoFetch: true.

| # | Event            | What it tells you                                 |
| - | ---------------- | ------------------------------------------------- |
| 1 | consentApproved  | Customer approved. autoFetch: true, so sit tight. |
| 2 | journeyCompleted | Customer is out of the AA flow.                   |
| 3 | dataReady        | Data is fetched and attached to the payload.      |

### 2. Two consents, both approved, automatic fetch

Five pushes total: consentApproved ×2, journeyCompleted ×1, dataReady ×2 — one data event per consent, each with its own consentHandle.

### 3. Consent approved, fetch on demand (autoFetch: false)

| # | Event            | What it tells you                                                        |
| - | ---------------- | ------------------------------------------------------------------------ |
| 1 | consentApproved  | autoFetch: false —**nothing further happens until you request a fetch**. |
| 2 | journeyCompleted | Customer is out of the AA flow.                                          |
| — | *(you act)*      | Request a fetch for that consentHandle; you are handed a requestId.      |
| 3 | dataReady        | sessionId equals the requestId you were given.                           |

This is the scenario that strands integrations built on dataReady alone: without consentApproved, nothing ever tells you it is your turn to act.

### 4. Multi-consent journey, customer approves only one

The AA reports nothing at all about a consent the customer ignored. After a short grace window (about two minutes, to let a late approval land) the untouched consent is declared NOT\_ACTED\_UPON.

| # | Event                                | What it tells you                             |
| - | ------------------------------------ | --------------------------------------------- |
| 1 | consentApproved                      | The consent the customer did approve.         |
| 2 | consentTerminated (NOT\_ACTED\_UPON) | The consent the customer ignored.             |
| 3 | journeyCompleted                     | Roster shows ACTIVE and PENDING side by side. |
| 4 | dataReady                            | For the approved consent only.                |

This is what makes the consent axis complete: every consentHandle reaches a terminal consent event, so you never interpret silence.

:::BlockQuote
A late approval is allowed to win. If the AA reports a consent ACTIVE after it was declared NOT\_ACTED\_UPON, the consent proceeds and its data is delivered normally — you would see the consent declared dead, then real data arrive for it. Suppressing a genuine delivery to protect the earlier guess would lose data.
:::

### 5. Customer declines at the AA

| # | Event                          | What it tells you                        |
| - | ------------------------------ | ---------------------------------------- |
| 1 | consentTerminated (REJECTED)   | terminatedBy USER; that consent is dead. |
| 2 | journeyFailed (USER\_REJECTED) | errorCode 1, stage CONSENT\_APPROVAL.    |

No data event is sent. The consent never became ACTIVE, so there was never a fetch to fail.

### 6. Customer walks away

| SITUATION                                      | EVENT                                                               |
| ---------------------------------------------- | ------------------------------------------------------------------- |
| Browser returns from the AA with no result     | journeyFailed, reason USER\_ABANDONED                               |
| Bank-selection link never opened, then expired | journeyFailed, reason EXPIRED, stage BANK\_SELECTION                |
| Customer lost at the AA, journey times out     | journeyFailed, reason EXPIRED, stage AA\_LOGIN                      |
| No accounts found at the selected banks        | journeyFailed, reason ACCOUNT\_NOT\_DISCOVERED, stage AA\_DISCOVERY |
| Accounts found but linking failed              | journeyFailed, reason ACCOUNT\_NOT\_LINKED, stage AA\_LINKING       |

### 7. Some banks delivered, some failed

You get **dataReady**, not dataFailed.

| # | Event     | What it tells you                                                                                                                                                       |
| - | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 | dataReady | data\[] holds every account that arrived. status.fips\[] shows the FIP that ended FAILED, and status.failedAccounts\[] names the individual accounts that did not come. |

Read failedAccounts before treating the payload as complete.

### 8. Nothing could be fetched

| SITUATION                                 | EVENT                                                        |
| ----------------------------------------- | ------------------------------------------------------------ |
| Every FIP failed                          | dataFailed, reason FETCH\_FAILED, partialDataAvailable false |
| AA reported the session FAILED or EXPIRED | dataFailed, reason SESSION\_FAILED / SESSION\_EXPIRED        |

On the SESSION\_\* reasons, check partialDataAvailable: true means some accounts were already stored before the session died and are readable via the data API.

### 9. Consent revoked or expired later

| SITUATION                                        | EVENT                                                  |
| ------------------------------------------------ | ------------------------------------------------------ |
| Customer revokes a consent, possibly weeks later | consentTerminated, reason REVOKED, terminatedBy USER   |
| The consent's validity window ends               | consentTerminated, reason EXPIRED, terminatedBy SYSTEM |

Both carry dataDelivered, which tells you whether the consent ever produced data before it died. For a PERIODIC consent, either event means the recurring fetch series has stopped — stop requesting fetches for that consentHandle.

### 10. Periodic consent, repeated fetches

A PERIODIC consent keeps serving fetches until it expires. Each fetch you request opens a new session and produces its own dataReady with a distinct sessionId. The consent stays ACTIVE throughout; you receive no further consent events until it is revoked or expires.

## Delivery Semantics

1. **Transport.** HTTP POST with a JSON body to the journey's callbackUrl.
2. **Respond fast.** Your endpoint must return 2xx within **10 seconds**. Do any heavy work asynchronously, after responding.
3. **No retries.** A push that fails or times out is not retried. Treat callbacks as the fast path, not the only path: if a journey has gone quiet, reconcile with the status and data APIs.
4. **De-duplicate.** Key on eventId — for dataReady, on (consentHandle, sessionId). Do not rely on arrival order; events for the same journey can arrive in any sequence.
5. **Keep the URL secret.** Possession of the callbackUrl is what authorises a push, so use an unguessable HTTPS endpoint.

## Integration Requirements

| ID    | REQUIREMENT                                                                                                                           |
| ----- | ------------------------------------------------------------------------------------------------------------------------------------- |
| FIU-1 | Accept POST with a JSON body and respond 2xx within 10 seconds.                                                                       |
| FIU-2 | Switch on event (or category) and**ignore values you do not recognise**, so new events are non-breaking.                              |
| FIU-3 | De-duplicate on eventId; for dataReady, on (consentHandle, sessionId). Do not depend on arrival order.                                |
| FIU-4 | Never treat the absence of a push as either success or failure. Reconcile with the status and data APIs.                              |
| FIU-5 | If you subscribe to consent events, stay available indefinitely — REVOKED and EXPIRED can arrive long after the journey has finished. |
| FIU-6 | Read status.failedAccounts\[] on dataReady before treating a payload as complete.                                                     |

