diff --git a/api-references/payments/billpay/api-integration.json b/api-references/payments/billpay/api-integration.json index 99c77ac0..fffbe6b9 100644 --- a/api-references/payments/billpay/api-integration.json +++ b/api-references/payments/billpay/api-integration.json @@ -9714,6 +9714,12 @@ "type": "boolean", "description": "Flag indicating if the Biller accepts adhoc payment", "example": true + }, + "supportDocumentUpload": { + "type": "boolean", + "description": "Flag indicating if the FX Retail biller supports document upload.", + "example": true, + "x-omitempty": true } }, "x-go-name": "CouBillerDetails" @@ -13053,7 +13059,7 @@ "tags": [ "Val Add APIs" ], - "description": "Initiate a Val Add operation", + "description": "Initiate a Val Add operation. For FX Retail in-app flows, call GetBankMarkup first, then ValidateCardOrAccount, ShareDetails, optional DocumentUpload, FetchBestPrice, and FetchAmountBreakup.", "parameters": [ { "name": "requestType", @@ -13066,7 +13072,10 @@ "GenerateOTP", "ValidateOTP", "GetBankMarkup", - "FetchBestPrice" + "ValidateCardOrAccount", + "ShareDetails", + "FetchBestPrice", + "FetchAmountBreakup" ] } }, @@ -13142,7 +13151,7 @@ }, "inputParams": { "type": "array", - "description": "Input parameters required by the biller for this request.", + "description": "Input parameters required by the biller for this request. For FX Retail in-app customer-param mapping, biller MDM uses ValidateCard, ValidateAccount, ShareDetailsForCardReload, and ShareDetailsForCardIssuance; these map to ValidateCardOrAccount and ShareDetails through journeyType.", "items": { "required": [ "name", @@ -13160,6 +13169,22 @@ } } } + }, + "journeyType": { + "type": "string", + "description": "Required for FX Retail in-app ValidateCardOrAccount and ShareDetails requests. Use Reload for forex card reload and Issuance for forex card issuance.", + "enum": [ + "Reload", + "Issuance" + ], + "example": "Reload", + "x-omitempty": true + }, + "refId": { + "type": "string", + "description": "Required for FX Retail in-app FetchAmountBreakup. Must be the successful sticky ShareDetails refId.", + "example": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", + "x-omitempty": true } } }, @@ -13344,6 +13369,92 @@ ] } }, + "validateCardOrAccount": { + "summary": "Validate Card or Account (FX Retail in-app only)", + "value": { + "agent": { + "id": "AX01AX26INBU00000001", + "channel": "INTB", + "ip": "124.170.23.24", + "mac": "48-4D-7E-CB-DB-6F" + }, + "biller": { + "id": "FXRE00000KER3U" + }, + "journeyType": "Reload", + "inputParams": [ + { + "name": "deliveryMode", + "value": "Forex Card Load" + }, + { + "name": "mobileNumber", + "value": "9182583612" + }, + { + "name": "customerId", + "value": "IN0025000213" + }, + { + "name": "pan", + "value": "JS221234A" + }, + { + "name": "passportNumber", + "value": "A1234567" + }, + { + "name": "forexCardLast4", + "value": "0345" + } + ] + } + }, + "shareDetails": { + "summary": "Share Details (FX Retail in-app only)", + "value": { + "agent": { + "id": "AX01AX26INBU00000001", + "channel": "INTB", + "ip": "124.170.23.24", + "mac": "48-4D-7E-CB-DB-6F" + }, + "biller": { + "id": "CCILFOREXNAT01" + }, + "journeyType": "Issuance", + "inputParams": [ + { + "name": "mobileNumber", + "value": "8838151414" + }, + { + "name": "customerId", + "value": "IN057983074" + }, + { + "name": "deliveryMode", + "value": "Forex Card Issuance" + }, + { + "name": "pan", + "value": "JS00024252" + }, + { + "name": "passportNumber", + "value": "A2345678" + }, + { + "name": "ifsc", + "value": "ZSBL0000341" + }, + { + "name": "bankBranch", + "value": "DELHI" + } + ] + } + }, "fetchBestPrice": { "summary": "Fetch Best Price", "value": { @@ -13419,6 +13530,35 @@ } ] } + }, + "fetchAmountBreakup": { + "summary": "Fetch Amount Breakup (FX Retail in-app only)", + "value": { + "agent": { + "id": "AX01AX26INBU00000001", + "channel": "INTB", + "ip": "124.170.23.24", + "mac": "48-4D-7E-CB-DB-6F" + }, + "biller": { + "id": "CCILFOREXNAT01" + }, + "refId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", + "inputParams": [ + { + "name": "mobileNumber", + "value": "8838151414" + }, + { + "name": "customerId", + "value": "IN057983074" + }, + { + "name": "deliveryMode", + "value": "Forex Card Issuance" + } + ] + } } } } @@ -13493,7 +13633,7 @@ "tags": [ "Val Add APIs" ], - "description": "Poll for the result of a Val Add operation, as a fallback for webhooks.", + "description": "Poll for the result of a Val Add operation, as a fallback for webhooks. For FX Retail in-app flows, ShareDetails returns the sticky journey refId and FetchAmountBreakup reuses that refId.", "parameters": [ { "name": "requestType", @@ -13506,7 +13646,10 @@ "GenerateOTP", "ValidateOTP", "GetBankMarkup", - "FetchBestPrice" + "ValidateCardOrAccount", + "ShareDetails", + "FetchBestPrice", + "FetchAmountBreakup" ] } }, @@ -13625,12 +13768,32 @@ } } } + }, + "additionalInfo": { + "type": "array", + "description": "Additional key-value pairs returned by the biller for the Val Add operation, when available.", + "items": { + "required": [ + "name", + "value" + ], + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "customerId" + }, + "value": { + "type": "string", + "example": "IN0025000213" + } + } + } } } } } - } - , + }, "examples": { "exampleGetCustomerIdSuccess": { "summary": "Get Customer ID Success", @@ -13790,6 +13953,58 @@ } } }, + "exampleValidateCardOrAccountSuccess": { + "summary": "Validate Card or Account Success", + "value": { + "refId": "KRQQESSWEAPQAHVHFQSNGBGZCZS51421211", + "status": "Success", + "traceId": "CV4PE82LTNJE9O014OE1", + "data": { + "billerResponse": [ + { + "name": "validationStatus", + "value": "Successful" + }, + { + "name": "description", + "value": "Card is Active" + }, + { + "name": "customerName", + "value": "Jane Doe" + } + ], + "additionalInfo": [], + "errors": [] + } + } + }, + "exampleShareDetailsSuccess": { + "summary": "Share Details Success", + "value": { + "refId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", + "status": "Success", + "traceId": "CV4PE82LTNJE9O014OE2", + "data": { + "billerResponse": [ + { + "name": "customerId", + "value": "IN057983074" + }, + { + "name": "customerType", + "value": "RESIDENTINDIVIDUAL" + }, + { + "name": "documentList", + "value": "Passport, PAN, VISA, TICKETS, Aadhaar" + } + ], + "additionalInfo": [], + "errors": [] + } + } + }, "exampleFetchBestPriceSuccess": { "summary": "Fetch Best Price Success", "value": { @@ -13830,6 +14045,36 @@ } } }, + "exampleFetchAmountBreakupSuccess": { + "summary": "Fetch Amount Breakup Success", + "value": { + "refId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", + "status": "Success", + "traceId": "CV4PE82LTNJE9O014OE4", + "data": { + "billerResponse": [ + { + "name": "baseAmount", + "value": "109999.00" + }, + { + "name": "charges", + "value": "100.00" + }, + { + "name": "tax", + "value": "30.00" + }, + { + "name": "totalPayableAmount", + "value": "110129.00" + } + ], + "additionalInfo": [], + "errors": [] + } + } + }, "exampleProcessing": { "summary": "Operation Still Processing", "value": { @@ -14005,12 +14250,376 @@ ] } }, + "/api/v2/bbps/valadd/documentUpload/request": { + "post": { + "tags": [ + "Val Add APIs" + ], + "description": "Upload documents for an FX Retail in-app ShareDetails flow when documentList indicates that documents are required. Send multipart/form-data with metadata and a ZIP file less than 5 MB. Files inside the ZIP must be named exactly as the comma-separated values returned in ShareDetails documentList. Multiple DocumentUpload requests can be made for the same origRefId when all documents cannot fit into a single ZIP under 5 MB.", + "operationId": "documentUploadRequest", + "parameters": [ + { + "name": "X-PARTNER-ID", + "in": "header", + "description": "Partner ID", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string", + "pattern": "^Bearer [a-zA-Z0-9\\-\\._~\\+\\/]+=*$" + }, + "description": "Bearer token for authentication. Format: `Bearer `", + "example": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." + } + ], + "requestBody": { + "required": true, + "content": { + "multipart/form-data": { + "schema": { + "required": [ + "metadata", + "file" + ], + "type": "object", + "properties": { + "metadata": { + "type": "string", + "description": "JSON metadata for the upload. origRefId must be the successful sticky ShareDetails refId.", + "example": "{\"origRefId\":\"D7O85FFK9A5CO0ODBA9GnYjOnkC61181519\",\"agent\":{\"id\":\"AX01AX26INBU00000001\",\"channel\":\"INTB\"},\"biller\":{\"id\":\"CCILFOREXNAT01\"},\"inputParams\":[{\"name\":\"mobileNumber\",\"value\":\"8838151414\"},{\"name\":\"customerId\",\"value\":\"IN057983074\"},{\"name\":\"deliveryMode\",\"value\":\"Forex Card Issuance\"}]}" + }, + "file": { + "type": "string", + "format": "binary", + "description": "ZIP file less than 5 MB. File names inside the ZIP must exactly match the ShareDetails documentList values, for example Passport, PAN, VISA, TICKETS, and Aadhaar." + } + } + }, + "encoding": { + "metadata": { + "contentType": "application/json" + }, + "file": { + "contentType": "application/zip" + } + } + } + } + }, + "responses": { + "200": { + "description": "Document upload request acknowledged", + "content": { + "application/json": { + "schema": { + "required": [ + "data", + "status", + "traceId" + ], + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "Processing" + }, + "data": { + "required": [ + "refId", + "origRefId" + ], + "type": "object", + "properties": { + "refId": { + "type": "string", + "description": "COU-generated document upload refId used for polling this upload.", + "example": "DOCUMENTUPLOADREFID123456789012345" + }, + "origRefId": { + "type": "string", + "description": "The successful sticky ShareDetails refId to which these documents belong.", + "example": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519" + } + } + }, + "traceId": { + "type": "string", + "example": "CV4PE82LTNJE9O014OE3" + } + } + }, + "examples": { + "ack": { + "summary": "DocumentUpload ACK", + "value": { + "status": "Processing", + "traceId": "CV4PE82LTNJE9O014OE3", + "data": { + "refId": "DOCUMENTUPLOADREFID123456789012345", + "origRefId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519" + } + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "Production": [ + "bbps:partner" + ] + }, + { + "Sandbox": [ + "bbps:partner" + ] + }, + { + "QA": [ + "bbps:partner" + ] + } + ] + } + }, + "/api/v2/bbps/valadd/documentUpload/response": { + "post": { + "tags": [ + "Val Add APIs" + ], + "description": "Poll for the result of a DocumentUpload operation, as a fallback for webhooks.", + "operationId": "documentUploadResponse", + "parameters": [ + { + "name": "X-PARTNER-ID", + "in": "header", + "description": "Partner ID", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "Authorization", + "in": "header", + "required": true, + "schema": { + "type": "string", + "pattern": "^Bearer [a-zA-Z0-9\\-\\._~\\+\\/]+=*$" + }, + "description": "Bearer token for authentication. Format: `Bearer `", + "example": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "required": [ + "refId" + ], + "type": "object", + "properties": { + "refId": { + "type": "string", + "description": "The document upload refId returned by documentUpload/request.", + "example": "DOCUMENTUPLOADREFID123456789012345" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Document upload result retrieved", + "content": { + "application/json": { + "schema": { + "required": [ + "refId", + "status", + "traceId" + ], + "type": "object", + "properties": { + "status": { + "type": "string", + "description": "Current state of the Val Add operation.", + "example": "Success" + }, + "refId": { + "type": "string", + "description": "COU-generated reference ID for the Val Add operation.", + "example": "HENSVVR4QOS7X1UGPY7JGUV444P10102202" + }, + "traceId": { + "type": "string", + "example": "CV4PE82LTNJE9O014OE0" + }, + "data": { + "type": "object", + "description": "Val Add result payload (varies by requestType). On success, contains `billerResponse`. On failure, contains `errors`.", + "properties": { + "billerResponse": { + "type": "array", + "description": "Key-value pairs returned by the biller for the Val Add operation. Present when status is `Success`.", + "items": { + "required": [ + "name", + "value" + ], + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "customerId" + }, + "value": { + "type": "string", + "example": "IN0025000213" + } + } + } + }, + "errors": { + "type": "array", + "description": "Error details. Present when status is `Failure`.", + "items": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Error code identifying the failure reason.", + "example": "CUS007" + }, + "message": { + "type": "string", + "description": "Human-readable error description.", + "example": "Customer does not exist" + } + } + } + }, + "additionalInfo": { + "type": "array", + "description": "Additional key-value pairs returned by the biller for the Val Add operation, when available.", + "items": { + "required": [ + "name", + "value" + ], + "type": "object", + "properties": { + "name": { + "type": "string", + "example": "customerId" + }, + "value": { + "type": "string", + "example": "IN0025000213" + } + } + } + } + } + } + } + }, + "examples": { + "success": { + "summary": "DocumentUpload Success", + "value": { + "refId": "DOCUMENTUPLOADREFID123456789012345", + "status": "Success", + "traceId": "CV4PE82LTNJE9O014OE3", + "data": { + "billerResponse": [ + { + "name": "Document Submission", + "value": "Successful" + } + ], + "additionalInfo": [], + "errors": [] + } + } + }, + "processing": { + "status": "Processing", + "refId": "HENSVVR4QOS7X1UGPY7JGUV444P10102202", + "traceId": "CV4PE82LTNJE9O014OE3", + "data": null + }, + "failure": { + "status": "Failure", + "refId": "HENSVVR4QOS7X1UGPY7JGUV444P10102202", + "traceId": "CV4PE82LTNJE9O014OE0", + "data": { + "errors": [ + { + "code": "error-code", + "message": "Error message" + } + ] + } + } + } + } + } + }, + "400": { + "description": "Bad request" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "Production": [ + "bbps:partner" + ] + }, + { + "Sandbox": [ + "bbps:partner" + ] + }, + { + "QA": [ + "bbps:partner" + ] + } + ] + } + }, "/api/v2/bbps/bills/mandate/request": { "post": { "tags": [ "Mandate API" ], - "description": "Partner App requests COU to book an FX mandate after customer confirmation of the best price. For CASH transactions, this effectively means the mandate is placed.", + "description": "Partner App requests COU to book an FX manx`date after customer confirmation of the best price. For FX Retail in-app flows, send customer.customerParams.inApp = true and the top-level refId from the successful FetchAmountBreakup flow.", "parameters": [ { "name": "X-PARTNER-ID", @@ -14107,7 +14716,9 @@ "example": "IN0025000213" } } - } + }, + "description": "Customer parameters for mandate booking. For FX Retail in-app flows, include { name: \"inApp\", value: \"true\" } as per biller MDM customer params." + } } } }, @@ -14125,13 +14736,20 @@ }, "amount": { "type": "integer", - "example": 8260000 + "example": 110129, + "description": "Amount in paise. Continue sending this as an integer; do not send decimal JSON values such as 110129.00." }, "mandateRefId": { "type": "string", "example": "d30eb5d76250a2faa76c5a2a59c76cc3" } } + }, + "refId": { + "type": "string", + "description": "Required for FX Retail in-app sticky mandate flows. Must be the successful FetchAmountBreakup refId, which is the same sticky ShareDetails refId.", + "example": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", + "x-omitempty": true } } } @@ -14200,7 +14818,6 @@ ] } ] - } }, "/api/v2/bbps/bills/mandate/response": { "post": { @@ -14447,6 +15064,7 @@ ] } } + }, "components": { "schemas": {}, diff --git a/content/payments/billpay/api-integration/apis.mdx b/content/payments/billpay/api-integration/apis.mdx index 60c9c199..77212f70 100644 --- a/content/payments/billpay/api-integration/apis.mdx +++ b/content/payments/billpay/api-integration/apis.mdx @@ -140,7 +140,7 @@ In the response you either get the bill details, or "Processing" status if the b ### Val Add (Value Added Services) -Some BBPS billers support **value added services** (Val Add) such as customer onboarding / OTP verification / markup & best-price discovery flows. COU exposes these flows as asynchronous APIs (request + response) — with webhooks as the primary mechanism and polling as fallback. +Some BBPS billers support **value added services** (Val Add) such as customer onboarding, OTP verification, markup discovery, best-price discovery, and FX Retail in-app validation flows. COU exposes these flows as asynchronous APIs (request + response), with webhooks as the primary mechanism and polling as fallback. - **Initiate Val Add operation**: `/bbps/valadd/{requestType}/request` - **Poll Val Add result**: `/bbps/valadd/{requestType}/response` @@ -151,7 +151,19 @@ Some BBPS billers support **value added services** (Val Add) such as customer on - `GenerateOTP` - `ValidateOTP` - `GetBankMarkup` +- `ValidateCardOrAccount` (FX Retail in-app flow only) +- `ShareDetails` (FX Retail in-app flow only) - `FetchBestPrice` +- `FetchAmountBreakup` (FX Retail in-app flow only) + +For FX Retail in-app flows, call the existing `GetBankMarkup` API first, then call `ValidateCardOrAccount`, `ShareDetails`, optional `DocumentUpload`, existing `FetchBestPrice`, and finally `FetchAmountBreakup`. + +For FX Retail in-app customer-param mapping, biller MDM uses separate customer-param specs for `ValidateCard`, `ValidateAccount`, `ShareDetailsForCardReload`, and `ShareDetailsForCardIssuance`. The API route names remain `ValidateCardOrAccount` and `ShareDetails`; use `journeyType` to select the card reload or card issuance path. + +`DocumentUpload` is also FX Retail in-app only, but it uses a dedicated multipart endpoint instead of the `{requestType}` route: + +- **Upload documents**: `/bbps/valadd/documentUpload/request` +- **Poll document upload**: `/bbps/valadd/documentUpload/response`
@@ -293,6 +305,114 @@ Some BBPS billers support **value added services** (Val Add) such as customer on
+ +
+ + + Sample Request — ValidateCardOrAccount (FX Retail in-app only) + + + {` +{ + "agent": { + "id": "AX01AX26INBU00000001", + "channel": "INTB", + "ip": "124.170.23.24", + "mac": "48-4D-7E-CB-DB-6F" + }, + "biller": { + "id": "FXRE00000KER3U" + }, + "journeyType": "Reload", + "inputParams": [ + { "name": "deliveryMode", "value": "Forex Card Load" }, + { "name": "mobileNumber", "value": "9182583612" }, + { "name": "customerId", "value": "IN0025000213" }, + { "name": "pan", "value": "JS221234A" }, + { "name": "passportNumber", "value": "A1234567" }, + { "name": "forexCardLast4", "value": "0345" } + ] +} + `} +
+
+ +
+ + +
+ + + Sample Request — ShareDetails (FX Retail in-app only) + + + {` +{ + "agent": { + "id": "AX01AX26INBU00000001", + "channel": "INTB", + "ip": "124.170.23.24", + "mac": "48-4D-7E-CB-DB-6F" + }, + "biller": { + "id": "CCILFOREXNAT01" + }, + "journeyType": "Issuance", + "inputParams": [ + { "name": "mobileNumber", "value": "8838151414" }, + { "name": "customerId", "value": "IN057983074" }, + { "name": "deliveryMode", "value": "Forex Card Issuance" }, + { "name": "pan", "value": "JS00024252" }, + { "name": "passportNumber", "value": "A2345678" }, + { "name": "ifsc", "value": "ZSBL0000341" }, + { "name": "bankBranch", "value": "DELHI" } + ] +} + `} +
+
+ +
+ + +
+ + + Sample Request — DocumentUpload metadata (FX Retail in-app only) + + + {` +{ + "origRefId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", + "agent": { + "id": "AX01AX26INBU00000001", + "channel": "INTB" + }, + "biller": { + "id": "CCILFOREXNAT01" + }, + "inputParams": [ + { "name": "mobileNumber", "value": "8838151414" }, + { "name": "customerId", "value": "IN057983074" }, + { "name": "deliveryMode", "value": "Forex Card Issuance" }, + { "name": "pan", "value": "JS00024252" }, + { "name": "passportNumber", "value": "A2345678" }, + { "name": "ifsc", "value": "ZSBL0000341" }, + { "name": "bankBranch", "value": "DELHI" } + ] +} + `} +
+
+ +
+ +`DocumentUpload` must be sent as `multipart/form-data` with `metadata` as the JSON part above and `file` as a ZIP file. `origRefId` must be the successful `ShareDetails` `refId`. Each ZIP file must be less than `5 MB`; make multiple `DocumentUpload` requests for the same `origRefId` if the documents cannot be sent in a single ZIP under `5 MB`. + +The files inside the ZIP must be named exactly as the comma-separated values returned in the `ShareDetails` `documentList`. For example, if `documentList` is `Passport, PAN, VISA, TICKETS, Aadhaar`, the ZIP should contain five files named `Passport`, `PAN`, `VISA`, `TICKETS`, and `Aadhaar`. + +
+
@@ -335,7 +455,38 @@ Some BBPS billers support **value added services** (Val Add) such as customer on
-The response will acknowledge the request and provide a `refId`. You’ll receive the final outcome via webhook; alternatively, you can poll using `/bbps/valadd/{requestType}/response`. + +
+ + + Sample Request — FetchAmountBreakup (FX Retail in-app only) + + + {` +{ + "agent": { + "id": "AX01AX26INBU00000001", + "channel": "INTB", + "ip": "124.170.23.24", + "mac": "48-4D-7E-CB-DB-6F" + }, + "biller": { + "id": "CCILFOREXNAT01" + }, + "refId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", + "inputParams": [ + { "name": "mobileNumber", "value": "8838151414" }, + { "name": "customerId", "value": "IN057983074" }, + { "name": "deliveryMode", "value": "Forex Card Issuance" } + ] +} + `} +
+
+ +
+ +The response will acknowledge the request and provide a `refId`. You’ll receive the final outcome via webhook; alternatively, you can poll using `/bbps/valadd/{requestType}/response`. For FX Retail in-app flows, `ShareDetails` returns the sticky journey `refId`; `FetchAmountBreakup` must reuse that `refId`.
@@ -482,6 +633,88 @@ The response will acknowledge the request and provide a `refId`. You’ll receiv
+ +
+ + + Sample Response — ValidateCardOrAccount (Success) + + + {` +{ + "refId": "KRQQESSWEAPQAHVHFQSNGBGZCZS51421211", + "status": "Success", + "traceId": "CV4PE82LTNJE9O014OE1", + "data": { + "billerResponse": [ + { "name": "validationStatus", "value": "Successful" }, + { "name": "description", "value": "Card is Active" }, + { "name": "customerName", "value": "Jane Doe" } + ], + "additionalInfo": [], + "errors": [] + } +} + `} +
+
+ +
+ + +
+ + + Sample Response — ShareDetails (Success) + + + {` +{ + "refId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", + "status": "Success", + "traceId": "CV4PE82LTNJE9O014OE2", + "data": { + "billerResponse": [ + { "name": "customerId", "value": "IN057983074" }, + { "name": "customerType", "value": "RESIDENTINDIVIDUAL" }, + { "name": "documentList", "value": "Passport, PAN, VISA" } + ], + "additionalInfo": [], + "errors": [] + } +} + `} +
+
+ +
+ + +
+ + + Sample Response — DocumentUpload (Success) + + + {` +{ + "refId": "DOCUMENTUPLOADREFID123456789012345", + "status": "Success", + "traceId": "CV4PE82LTNJE9O014OE3", + "data": { + "billerResponse": [ + { "name": "Document Submission", "value": "Successful" } + ], + "additionalInfo": [], + "errors": [] + } +} + `} +
+
+ +
+
@@ -512,6 +745,35 @@ The response will acknowledge the request and provide a `refId`. You’ll receiv
+ +
+ + + Sample Response — FetchAmountBreakup (Success) + + + {` +{ + "refId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", + "status": "Success", + "traceId": "CV4PE82LTNJE9O014OE4", + "data": { + "billerResponse": [ + { "name": "baseAmount", "value": "109999.00" }, + { "name": "charges", "value": "100.00" }, + { "name": "tax", "value": "30.00" }, + { "name": "totalPayableAmount", "value": "110129.00" } + ], + "additionalInfo": [], + "errors": [] + } +} + `} +
+
+ +
+
@@ -538,7 +800,7 @@ The response will acknowledge the request and provide a `refId`. You’ll receiv ### FX Mandate booking -For the FX transaction flow, COU exposes APIs to **book an FX mandate** and to **poll the booking status** (fallback for webhooks). +For the FX transaction flow, COU exposes APIs to **book an FX mandate** and to **poll the booking status** (fallback for webhooks). For FX Retail in-app flows, include `customer.customerParams.inApp = true` and send the top-level `refId` from the successful `FetchAmountBreakup` flow. - **Book FX mandate**: `/bbps/bills/mandate/request` - **Poll FX mandate status**: `/bbps/bills/mandate/response` @@ -563,17 +825,18 @@ For the FX transaction flow, COU exposes APIs to **book an FX mandate** and to * }, "customer": { "customerParams": [ - { "name": "mobileNumber", "value": "8838151414" }, { "name": "customerId", "value": "IN0025000213" }, { "name": "currency", "value": "USD" }, { "name": "instrumentType", "value": "CASH" }, { "name": "markup", "value": "0.5" }, - { "name": "bestPrice", "value": "8505.5" } + { "name": "bestPrice", "value": "8505.5" }, + { "name": "inApp", "value": "true" } ] }, + "refId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", "mandate": { "mode": "UPI", - "amount": 8260000, + "amount": 110129, "mandateRefId": "d30eb5d76250a2faa76c5a2a59c76cc3" } } @@ -646,10 +909,6 @@ The **FX Retail (Val Add + Mandate flows)**: When the payment is being initiated as part of the **FX mandate** journey, include `paymentType: "MANDATE_AND_PAY"` in the request body of the Pay bill API. The rest of the Pay bill payload remains the same. - -> **FX Retail (Val Add + Mandate flows)**: When the payment is being initiated as part of the **FX mandate** journey, include `paymentType: "MANDATE_AND_PAY"` in the request body of the Pay bill API. The rest of the Pay bill payload remains the same. -
### Raise dispute @@ -684,9 +943,10 @@ For your convenience, Setu also provides actions to list data in bulk, that can FX Retail fields (present on every biller):
  • valAddFlag (boolean) — indicates if value-added services are enabled for the biller.
  • -
  • valAddCustParams (ValAddCustParamSpec[]) — customer parameters required for value-added service flows. Each entry includes requestType (e.g. MANDATE_AND_PAY for FX Retail mandate+pay) and the corresponding customerParams specs.
  • +
  • valAddCustParams (ValAddCustParamSpec[]) — customer parameters required for value-added service flows. For FX Retail in-app flows, biller MDM provides separate customer-param specs for ValidateCard, ValidateAccount, ShareDetailsForCardReload, and ShareDetailsForCardIssuance; these map to the ValidateCardOrAccount and ShareDetails API steps through journeyType. Other FX Retail request types include GetBankMarkup, FetchBestPrice, and FetchAmountBreakup.
  • mandateRequirement (string) — mandate requirement for the biller (e.g. MANDATORY). For FX Retail billers, use this to decide whether mandate+pay flow is required.
  • pseudoBiller (boolean) — indicates if the biller is a pseudo biller.
  • +
  • supportDocumentUpload (boolean) — indicates if the biller supports document upload.
  • diff --git a/content/payments/billpay/api-integration/fx-retail.mdx b/content/payments/billpay/api-integration/fx-retail.mdx index 0a17c40c..36ca723f 100644 --- a/content/payments/billpay/api-integration/fx-retail.mdx +++ b/content/payments/billpay/api-integration/fx-retail.mdx @@ -28,6 +28,16 @@ FX Retail biller objects also include these fields: - **`valAddCustParams`**: required customer params for value-added flows (scoped by requestType) - **`mandateRequirement`**: mandate requirement (e.g. `MANDATORY`) - **`pseudoBiller`**: whether the biller is a pseudo biller +- **`supportDocumentUpload`**: whether the biller supports document upload + +For the in-app flow, the biller MDM customer-param mapping is split more granularly than the API route names: + +| Biller MDM request type | API step | +| --- | --- | +| `ValidateCard` | `ValidateCardOrAccount` with `journeyType: "Reload"` | +| `ValidateAccount` | `ValidateCardOrAccount` with `journeyType: "Issuance"` | +| `ShareDetailsForCardReload` | `ShareDetails` with `journeyType: "Reload"` | +| `ShareDetailsForCardIssuance` | `ShareDetails` with `journeyType: "Issuance"` | ## Common requirements @@ -41,6 +51,8 @@ All API calls require the following headers: | `Authorization` | `Bearer ` from the token API | | `Content-Type` | `application/json` | +Use `multipart/form-data` instead of JSON only for the in-app `DocumentUpload` API. + ### Agent object Every Val Add and Mandate request includes an `agent` object. See [Agent](/payments/billpay/api-integration/objects#agent) object for the full schema and channel-specific requirements. @@ -68,10 +80,21 @@ To get the final result, use either: For the complete request/response schema and error codes, see the [Val Add API reference](/payments/billpay/api-integration/api-reference#/category~Val%20Add%20APIs) and [Mandate API reference](/payments/billpay/api-integration/api-reference#/category~Mandate%20API). The steps below cover the happy path with example payloads. +The new Phase II FX Retail APIs are only for the **in-app flow**. In that flow, call the existing **GetBankMarkup** API first, then call the in-app APIs in this order: **ValidateCardOrAccount**, **ShareDetails**, optional **DocumentUpload**, existing **FetchBestPrice**, and finally **FetchAmountBreakup** before mandate creation. + ## End-to-end flow (buy flow) The FX Retail flow is driven by **Val Add** APIs for onboarding + pricing, followed by **Mandate** booking (order placement), and finally a **Pay bill** request for BBPS/NPCI reconciliation. +For in-app purchases, the pricing and validation sequence after customer onboarding is: + +1. `GetBankMarkup` (existing API) +2. `ValidateCardOrAccount` (in-app only) +3. `ShareDetails` (in-app only) +4. `DocumentUpload`, if `ShareDetails` returns a required `documentList` (in-app only) +5. `FetchBestPrice` (existing API) +6. `FetchAmountBreakup` (in-app only) + ### Step 1 — Check if customer is onboarded (GetCustomerId) Check if the customer is already registered on the FX Retail platform using the customer's `mobileNumber`. @@ -366,7 +389,7 @@ If OTP validation succeeds, the FX Retail platform registers the user and return } }`} -> The `customerId` returned here is used in Steps 4 and 5. +> The `customerId` returned here is used in the later steps. ### Step 4 — Get bank markup (GetBankMarkup) @@ -463,9 +486,275 @@ If multiple banks are associated with the customer's FX account, multiple markup } }`} -> Present these banks to the customer. The selected bank's `bankId`, `homeBranchIFSC`, `rate`, `units`, and `relationshipBank` name are needed for FetchBestPrice. +> Present these banks to the customer. The selected bank's `bankId`, `homeBranchIFSC`, `rate`, `units`, and `relationshipBank` name are needed for the steps for in-app flow and FetchBestPrice. + +### Step 5 — Validate card or account (ValidateCardOrAccount, in-app only) + +For the in-app flow, validate the customer against the selected journey before sharing transaction details. + +- **Request**: `POST /api/v2/bbps/valadd/ValidateCardOrAccount/request` +- **Poll**: `POST /api/v2/bbps/valadd/ValidateCardOrAccount/response` + +Use `journeyType` to select the downstream validation path: + +| `journeyType` | Meaning | +| --- | --- | +| `Reload` | Forex card reload journey. COU validates the forex-card path. | +| `Issuance` | Forex card issuance journey. COU validates the account path. | + +**Request body** + +{`{ + "agent": { + "id": "AX01AX26INBU00000001", + "channel": "INTB", + "ip": "124.170.23.24", + "mac": "48-4D-7E-CB-DB-6F" + }, + "biller": { + "id": "FXRE00000KER3U" + }, + "journeyType": "Reload", + "inputParams": [ + { + "name": "deliveryMode", + "value": "Forex Card Load" + }, + { + "name": "mobileNumber", + "value": "9182583612" + }, + { + "name": "customerId", + "value": "IN0025000213" + }, + { + "name": "pan", + "value": "JS221234A" + }, + { + "name": "passportNumber", + "value": "A1234567" + }, + { + "name": "forexCardLast4", + "value": "0345" + } + ] +}`} + +**Success response** (from `/response` endpoint or webhook) + +{`{ + "refId": "KRQQESSWEAPQAHVHFQSNGBGZCZS51421211", + "status": "Success", + "traceId": "CV4PE82LTNJE9O014OE1", + "data": { + "billerResponse": [ + { + "name": "validationStatus", + "value": "Successful" + }, + { + "name": "description", + "value": "Card is Active" + }, + { + "name": "customerName", + "value": "Jane Doe" + } + ], + "additionalInfo": [], + "errors": [] + } +}`} + +### Step 6 — Share details (ShareDetails, in-app only) + +Share the customer's FX journey details with the biller. This step returns the sticky FX journey `refId` used by later in-app APIs. + +- **Request**: `POST /api/v2/bbps/valadd/ShareDetails/request` +- **Poll**: `POST /api/v2/bbps/valadd/ShareDetails/response` + +`journeyType` is required. Retain the returned `refId`; it is reused by `FetchAmountBreakup` and `Mandate` apis and becomes `origRefId` if document upload is required. + +**Request body** + +{`{ + "agent": { + "id": "AX01AX26INBU00000001", + "channel": "INTB", + "ip": "124.170.23.24", + "mac": "48-4D-7E-CB-DB-6F" + }, + "biller": { + "id": "CCILFOREXNAT01" + }, + "journeyType": "Issuance", + "inputParams": [ + { + "name": "mobileNumber", + "value": "8838151414" + }, + { + "name": "customerId", + "value": "IN057983074" + }, + { + "name": "deliveryMode", + "value": "Forex Card Issuance" + }, + { + "name": "pan", + "value": "JS00024252" + }, + { + "name": "passportNumber", + "value": "A2345678" + }, + { + "name": "ifsc", + "value": "ZSBL0000341" + }, + { + "name": "bankBranch", + "value": "DELHI" + } + ] +}`} -### Step 5 — Fetch best price (FetchBestPrice) +**Success response** (from `/response` endpoint or webhook) + +{`{ + "refId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", + "status": "Success", + "traceId": "CV4PE82LTNJE9O014OE2", + "data": { + "billerResponse": [ + { + "name": "customerId", + "value": "IN057983074" + }, + { + "name": "customerType", + "value": "RESIDENTINDIVIDUAL" + }, + { + "name": "documentList", + "value": "Passport, PAN, VISA" + } + ], + "additionalInfo": [], + "errors": [] + } +}`} + +Inspect `data.billerResponse` for `documentList`. + +| `documentList` value | Meaning | Next step | +| --- | --- | --- | +| Actual document names, for example `Passport, PAN, VISA` | Document upload is required | Call `DocumentUpload` before `FetchBestPrice` | +| `Not Applicable` | No document upload is required | Skip `DocumentUpload` | +| Empty or absent | No upload requirement is inferred by COU | Continue to `FetchBestPrice` | + +### Step 7 — Upload documents (DocumentUpload, in-app only and conditional) + +Upload documents only when `ShareDetails` returns an actual `documentList`. + +- **Request**: `POST /api/v2/bbps/valadd/documentUpload/request` +- **Poll**: `POST /api/v2/bbps/valadd/documentUpload/response` + +This is a dedicated multipart endpoint. Send `multipart/form-data` with exactly two form parts: + +- `metadata`: JSON metadata +- `file`: ZIP file, less than `5 MB` + +The `metadata.origRefId` must be the successful `ShareDetails` `refId`. + +The ZIP contents must match the `documentList` returned by `ShareDetails`: + +- create one file for each comma-separated document name in `documentList` +- name each file exactly the same as the corresponding `documentList` value +- keep each ZIP file under `5 MB` +- make multiple `DocumentUpload` requests for the same `origRefId` if the documents cannot be sent in a single ZIP under `5 MB` + +For example, if `ShareDetails` returns: + +{`{ + "name": "documentList", + "value": "Passport, PAN, VISA, TICKETS, Aadhaar" +}`} + +then the ZIP should contain five files named: + +1. `Passport` +2. `PAN` +3. `VISA` +4. `TICKETS` +5. `Aadhaar` + +**Metadata JSON** + +{`{ + "origRefId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", + "agent": { + "id": "AX01AX26INBU00000001", + "channel": "INTB" + }, + "biller": { + "id": "CCILFOREXNAT01" + }, + "inputParams": [ + { + "name": "mobileNumber", + "value": "8838151414" + }, + { + "name": "customerId", + "value": "IN057983074" + }, + { + "name": "deliveryMode", + "value": "Forex Card Issuance" + }, + { + "name": "pan", + "value": "JS00024252" + }, + { + "name": "passportNumber", + "value": "A2345678" + }, + { + "name": "ifsc", + "value": "ZSBL0000341" + }, + { + "name": "bankBranch", + "value": "DELHI" + } + ] +}`} + +**Success response** (from `/response` endpoint or webhook) + +{`{ + "refId": "DOCUMENTUPLOADREFID123456789012345", + "status": "Success", + "traceId": "CV4PE82LTNJE9O014OE3", + "data": { + "billerResponse": [ + { + "name": "Document Submission", + "value": "Successful" + } + ], + "additionalInfo": [], + "errors": [] + } +}`} + +### Step 8 — Fetch best price (FetchBestPrice) Customer selects a bank/markup and then the PSP app fetches the best currency price. @@ -598,13 +887,87 @@ The following fields are carried over from earlier steps: } }`} -> `totalPayableAmount` is in paise and includes a buffer for price fluctuation. This is the amount the PSP should debit in Step 6. +> `totalPayableAmount` is in paise and includes a buffer for price fluctuation. This is the amount the PSP should debit in Step 10. -### Step 6 — Debit funds from the customer (PSP-managed) +### Step 9 — Fetch amount breakup (FetchAmountBreakup, in-app only) -The PSP app debits the `totalPayableAmount` (from Step 5) from the customer's account — this happens **outside** of BBPS COU APIs. +For the in-app flow, fetch the amount breakup after FetchBestPrice and before mandate creation. This returns the final payable amount for the in-app mandate flow. -### Step 7 — Place order by booking mandate (Mandate request/response) +- **Request**: `POST /api/v2/bbps/valadd/FetchAmountBreakup/request` +- **Poll**: `POST /api/v2/bbps/valadd/FetchAmountBreakup/response` + +Request rules: + +- send a top-level `refId` +- the `refId` must be the successful sticky `ShareDetails` `refId` +- if document upload was required by `ShareDetails`, complete `DocumentUpload` successfully before this request + +**Request body** + +{`{ + "agent": { + "id": "AX01AX26INBU00000001", + "channel": "INTB", + "ip": "124.170.23.24", + "mac": "48-4D-7E-CB-DB-6F" + }, + "biller": { + "id": "CCILFOREXNAT01" + }, + "refId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", + "inputParams": [ + { + "name": "mobileNumber", + "value": "8838151414" + }, + { + "name": "customerId", + "value": "IN057983074" + }, + { + "name": "deliveryMode", + "value": "Forex Card Issuance" + } + ] +}`} + +**Success response** (from `/response` endpoint or webhook) + +{`{ + "refId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", + "status": "Success", + "traceId": "CV4PE82LTNJE9O014OE4", + "data": { + "billerResponse": [ + { + "name": "baseAmount", + "value": "109999.00" + }, + { + "name": "charges", + "value": "100.00" + }, + { + "name": "tax", + "value": "30.00" + }, + { + "name": "totalPayableAmount", + "value": "110129.00" + } + ], + "additionalInfo": [], + "errors": [] + } +}`} + +For in-app flows, retain this same sticky `refId` for the Mandate request. `mandate.amount` must still be sent as an integer amount in paise. + +### Step 10 — Debit funds from the customer (PSP-managed) + +The PSP app debits the `totalPayableAmount` from the customer's account. For in-app flows, use Step 9. For non-in-app flows, use Step 8. This debit happens **outside** of BBPS COU APIs. + +### Step 11 — Place order by booking mandate (Mandate request/response) Initiate the mandate booking request. For FX Retail, this effectively places the order on the FX Retail platform. @@ -617,7 +980,7 @@ On success, the mandate result includes the **actual amount utilised** for the o **Request body** -The `customerId` is from Step 1 or Step 3, and `amount` is the `totalPayableAmount` from Step 5 (in paise). +The `customerId` is from Step 1 or Step 3. For in-app flows, include `customer.customerParams.inApp = true` as per the biller mdm customer params, send the top-level sticky `refId` from Step 9, and send `mandate.amount` as an integer amount in paise. {`{ "agent": { @@ -634,9 +997,14 @@ The `customerId` is from Step 1 or Step 3, and `amount` is the `totalPayableAmou { "name": "customerId", "value": "IN0025000213" + }, + { + "name": "inApp", + "value": "true" } ] }, + "refId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", "mandate": { "mode": "UPI", "amount": 8506000, @@ -648,11 +1016,11 @@ The `customerId` is from Step 1 or Step 3, and `amount` is the `totalPayableAmou {`{ "status": "Success", - "refId": "HENSVVR4QOS7X1UGPY7JGUV444P10102202", + "refId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", "traceId": "CV6PG04MUNJG9P126QG3", "data": { - "billerRefId": "HENSVVR4QOS7X1UGPY7JGUV444P10102202", - "transactionId": "HENSVVR4QOS7X1UGPY7JGUV444P10102202", + "billerRefId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", + "transactionId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", "billerResponse": { "amount": "8260000" }, @@ -670,18 +1038,18 @@ The `customerId` is from Step 1 or Step 3, and `amount` is the `totalPayableAmou > The mandate can fail if the market price moves above the buffered amount. -### Step 8 — Refund the difference (PSP-managed) +### Step 12 — Refund the difference (PSP-managed) If the customer was debited a buffered amount, the PSP app should: - compute the delta between (debited amount) and (amount utilised returned in mandate result) - refund the difference back to the customer -Compute: `totalPayableAmount` (Step 5) minus `billerResponse.amount` (Step 7). Refund this delta to the customer. +Compute: `totalPayableAmount` (Step 8) minus `billerResponse.amount` (Step 11). Refund this delta to the customer. This refund happens **outside** of BBPS COU APIs. -### Step 9 — Send Pay bill request for reconciliation (Pay bill) +### Step 13 — Send Pay bill request for reconciliation (Pay bill) Finally, call **Pay bill** to complete BBPS/NPCI reconciliation. For FX Retail mandate flows, the payment has already been collected in the earlier PSP debit + mandate placement, so this API is used for reconciliation and settlement workflows. @@ -693,7 +1061,7 @@ For FX Retail mandate-related payments, include: **Request body** -The `refId` is the mandate's `refId` from Step 7. +The `refId` is the mandate's `refId` from Step 11. {`{ "agent": { @@ -715,7 +1083,7 @@ The `refId` is the mandate's `refId` from Step 7. ] }, "paymentDetails": { - "amount": 8260000, + "amount": 110129, "mode": "Internet Banking", "paymentRefId": "BD019181220291", "timestamp": "2020-12-12T13:12:00+05:30" @@ -725,7 +1093,7 @@ The `refId` is the mandate's `refId` from Step 7. "email": "jayasurya.s_tra@npci.org.in", "pan": "JS00024252" }, - "refId": "HENSVVR4QOS7X1UGPY7JGUV444P10102202", + "refId": "D7O85FFK9A5CO0ODBA9GnYjOnkC61181519", "paymentType": "MANDATE_AND_PAY" }`} @@ -750,7 +1118,8 @@ When an API call fails, the response follows this format: Common failure scenarios: - **Invalid OTP**: The OTP provided in ValidateOTP is incorrect or expired. Retry GenerateOTP to request a new OTP. -- **Price movement exceeding buffer**: The market price moved above the buffered `totalPayableAmount` between FetchBestPrice and the mandate request. Re-run FetchBestPrice to get updated pricing. +- **Price movement exceeding buffer**: The market price moved above the buffered `totalPayableAmount` before mandate placement. Re-run FetchBestPrice, and for in-app flows re-run FetchAmountBreakup, to get updated pricing. +- **Invalid sticky reference**: The in-app `refId` is missing, stale, or not linked to a successful ShareDetails/FetchAmountBreakup flow. Restart the in-app sequence from ShareDetails. - **Customer not found**: The `customerId` is invalid or does not exist on the FX Retail platform. Verify the customer is registered via GetCustomerId. ## Webhooks vs polling diff --git a/content/payments/billpay/api-integration/webhooks.mdx b/content/payments/billpay/api-integration/webhooks.mdx index 65544a31..2bb54b42 100644 --- a/content/payments/billpay/api-integration/webhooks.mdx +++ b/content/payments/billpay/api-integration/webhooks.mdx @@ -178,7 +178,9 @@ If this field is absent or null, the callback corresponds to a manual bill fetch ### Val Add webhooks -Val Add APIs (Value Added Services) are asynchronous. After you initiate a Val Add request using `/bbps/valadd/{requestType}/request`, COU will send you a webhook with the final outcome for that operation. +Val Add APIs (Value Added Services) are asynchronous. After you initiate a Val Add request using `/bbps/valadd/{requestType}/request`, COU will send you a webhook with the final outcome for that operation. `DocumentUpload` uses the dedicated `/bbps/valadd/documentUpload/request` endpoint, but its final outcome is still delivered as a Val Add webhook. + +For FX Retail in-app flows, the Val Add APIs reuse this callback surface. The in-app flow uses the existing `GetBankMarkup` event first, then `ValidateCardOrAccount`, `ShareDetails`, optional `DocumentUpload`, existing `FetchBestPrice`, and finally `FetchAmountBreakup`. Common `event` values include: @@ -186,7 +188,22 @@ Common `event` values include: - `VAL_ADD_GENERATE_OTP` - `VAL_ADD_VALIDATE_OTP` - `VAL_ADD_GET_BANK_MARKUP` +- `VAL_ADD_VALIDATE_CARD_OR_ACCOUNT` +- `VAL_ADD_SHARE_DETAILS` - `VAL_ADD_FETCH_BEST_PRICE` +- `VAL_ADD_FETCH_AMOUNT_BREAKUP` +- `VAL_ADD_DOCUMENT_UPLOAD` + +>FX Retail in-app `refId` behavior: + +| API | Webhook event | `refId` behavior | +| --- | --- | --- | +| `ValidateCardOrAccount` | `VAL_ADD_VALIDATE_CARD_OR_ACCOUNT` | COU returns a unique validation `refId`. It is not reused later. | +| `ShareDetails` | `VAL_ADD_SHARE_DETAILS` | COU returns the sticky FX journey `refId`. Reuse it for `FetchAmountBreakup`; use it as `origRefId` for document upload. | +| `DocumentUpload` | `VAL_ADD_DOCUMENT_UPLOAD` | COU returns a document upload `refId` for upload tracking. The upload request metadata must carry the `ShareDetails` `refId` as `origRefId`. | +| `FetchAmountBreakup` | `VAL_ADD_FETCH_AMOUNT_BREAKUP` | The callback uses the sticky `ShareDetails` `refId`, which is also used by the in-app mandate flow. | + +>
    @@ -211,7 +228,7 @@ Common `event` values include:
    -> **Fallback**: If you don’t receive a webhook, you can poll the result using `/bbps/valadd/{requestType}/response` with the `refId`. +> **Fallback**: If you don’t receive a webhook, you can poll the result using `/bbps/valadd/{requestType}/response` with the `refId`. For `DocumentUpload`, you can poll the response using `/bbps/valadd/documentUpload/response`