Consent Start
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:
- start → create the consent(s), get journeyId + redirectUrl.
- Redirect the customer to redirectUrl — the AA consent screen (if you passed fipIds) or Signzy's bank-selection screen (if you didn't).
- Receive callback events on your callbackUrl as the journey progresses — see Callback Events below.
- 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: 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
Finvu ---> "aaId": "[email protected]", Saafe ---> "aaId": "dashboard-aa-preprod", NADL ---> "aaId": "AA00023403", Anumati ---> "aaId": "Anumati-UAT", CamsFinserv ---> "aaId": "AA00022222", Onemoney ---> "aaId": "onemoney-aa"
Production
Finvu ---> "aaId": "cookiejaraalive@finvu", Saafe --> "aaId": "saafe", Onemoney ---> "aaId": "onemoney", NADL ---> "aaId": "AA00023404", Anumati ---> "aaId": "Anumati", camsfinserv ---> "aaId": "AA00022277"
fipIds mapping
UAT
PROD
In UAT for Success Flow select Millenium Bank account ending with XXX5877, XXX5876 in Account Aggregator UI.
- 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
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"
}'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 — seeCallback 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
{
"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:
- 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.
- 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:
{
"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 thefact 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. |
Event Payloads
{
"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.
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. |
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. |
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.
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
- Transport. HTTP POST with a JSON body to the journey's callbackUrl.
- Respond fast. Your endpoint must return 2xx within 10 seconds. Do any heavy work asynchronously, after responding.
- 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.
- 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.
- 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) andignore 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. |