Compliance remediation is driven asynchronously by webhooks — not by a fixed amount threshold. It is decoupled from the main Payin flow: the payin can keep progressing while remediation is handled separately via SUB_VIRTUAL_ACCOUNT_RFI_REQUIRED.
1. Overview#
When a Sub Virtual Account (Sub VA) requires additional compliance materials (RFI — Request for Information), the platform notifies the merchant via webhook, then the merchant:1.
Receives Sub Virtual Account RFI Required (push), and/or polls List RFI Sub Virtual Account
2.
Calls Get Sub Virtual Account RFI Template Info to load the question / file checklist
3.
Calls Sub Virtual Account Submit RFI to upload answers and documents
4.
Continues to receive the same webhook (or re-list) as review progresses — use rfiStatus for the latest outcome
Quick steps#
1.
Webhook (async): SUB_VIRTUAL_ACCOUNT_RFI_REQUIRED
2.
GET /v1/virtual-account/sub/rfi-template
3.
POST /v1/virtual-account/sub/submit-rfi
4.
Poll GET /v1/virtual-account/sub/rfi-list / GET /v1/virtual-account/sub/get until rfiStatus / account or order status updates
Integration notes#
Before remediation completes, Payin / Payout orders may stay in intermediate states for a long time.
Do not blindly re-fire payout while status is RESUBMIT / RFI incomplete.
2. Webhook — Sub Virtual Account RFI Required#
How to use#
1.
Configure your Stable webhook URL to accept this event.
2.
On receive, deduplicate by eventId.
3.
Branch on data.rfiStatus (see table below).
4.
If materials are required, call the template API with virtualAccountId + bizTypeList, then submit RFI.
5.
Keep listening to this same event for submit / review status updates.
Critical fields#
| Field | Why it matters | How to use |
|---|
eventId | Idempotency | Ignore duplicate deliveries with the same id |
eventType | Event filter | Must be SUB_VIRTUAL_ACCOUNT_RFI_REQUIRED |
data.virtualAccountId | Identifies the Sub VA | Template: virtualAccountId; Submit: accountId |
data.awaitingRfiTypeList | Which tiers need materials | Template: bizTypeList (1:1) |
data.rfiStatus | Lifecycle / review result | Branch merchant logic; review results reuse this webhook |
data.rfiFeedback | Reject / resubmit reason | Present when rfiStatus is REJECTED or RESUBMIT_REQUIRED; show to end users / ops |
referenceId | Envelope reference | Usually VA id; prefer data.virtualAccountId for API calls |
Example payload#
{
"eventId": "36190155-28a2-42f9-a55a-1359654b7384",
"eventType": "SUB_VIRTUAL_ACCOUNT_RFI_REQUIRED",
"referenceId": "538",
"status": "SUCCESS",
"timestamp": 1764235978954,
"data": {
"virtualAccountId": "3100",
"externalAccountId": "",
"accountName": "ALBrooks",
"currency": "MXN",
"status": "SUCCESS",
"awaitingRfiTypeList": [
"SUB_ACCOUNT_RFI_TIER_1"
],
"rfiStatus": "AWAITING_SUBMISSION"
}
}
rfiStatus values#
rfiStatus | Meaning | Merchant action |
|---|
AWAITING_SUBMISSION | Materials required | Fetch template → submit RFI |
EARLY_WARNING | Early warning; materials may be submitted early | Optional early submit |
RESET | Previously triggered; monthly reset; still submittable | May submit again |
RESUBMIT_REQUIRED | Compliance requested resubmission | Fetch template → resubmit |
SUBMITTED | Materials received; under review | Wait for next webhook |
APPROVED | Review approved | No further RFI action |
REJECTED | Review rejected | Follow compliance guidance |
3. API — List RFI Sub Virtual Account#
How to use#
1.
Call this API to poll / reconcile Sub VAs that need RFI (or to refresh status after submit / review).
2.
Optionally filter with rfiStatus (e.g. AWAITING_SUBMISSION) and/or currency.
3.
For each item that still needs materials:accountId → template virtualAccountId / submit accountId
awaitingRfiTypeList → template bizTypeList
4.
Continue with Get Sub Virtual Account RFI Template Info → Sub Virtual Account Submit RFI.
Critical fields#
| Parameter | Required | Note |
|---|
rfiStatus | No | Common filter: AWAITING_SUBMISSION, RESUBMIT_REQUIRED, SUBMITTED, APPROVED, REJECTED, … |
currency | No | ISO 4217 (e.g. MXN) |
accountBusinessType | No | Defaults to Normal on the request model |
parentAccountId | No | Optional parent (primary) account id |
| Field | Why it matters | How to use |
|---|
accountId | Sub VA id | Same as webhook virtualAccountId; pass to template + submit |
rfiStatus | Account-level RFI status | Branch list / polling logic |
awaitingRfiTypeList | Tiers still needing materials | Template bizTypeList |
rfiFeedback | Reject / resubmit reason | Non-null when rfiStatus is REJECTED / RESUBMIT_REQUIRED |
rfiInfo.rfiTierList[] | Per-tier status + answers | Resubmit / display previously submitted answerList |
Example request#
This list API is a pull complement to the webhook push. Prefer the webhook for real-time updates; use list for catch-up, reconciliation, and UI listing.
4. API — Get Sub Virtual Account RFI Template Info#
How to use#
1.
Call this API only after a webhook with a submittable rfiStatus (e.g. AWAITING_SUBMISSION, RESUBMIT_REQUIRED).
2.
Pass query params mapped from the webhook (see Critical fields).
3.
For each template in data[], iterate questions[] and prepare submit answers:questions[].id → submit questionId
template bizType → submit bizType
type = FILE → prepare base64Files / file; text-like → prepare answer
4.
Next step: Sub Virtual Account Submit RFI.
Critical fields#
| Parameter | Required | Map from webhook | Note |
|---|
bizTypeList | Yes | data.awaitingRfiTypeList | Wrong/missing → empty or incomplete checklist |
virtualAccountId | Recommended | data.virtualAccountId | Needed to resolve channel-specific template |
currency | Optional | data.currency | ISO 4217 |
Response (data[]) — fields you must carry to submit| Field | How to use on submit |
|---|
bizType | answerList[].bizType |
questions[].id | answerList[].questionId (exact match) |
questions[].type | FILE → files; TEXT → answer |
questions[].required | Must provide content when true |
Example request#
Do not hard-code question ids from samples — always load them from this API for the current virtualAccountId / channel.
5. API — Sub Virtual Account Submit RFI#
How to use#
1.
Ensure you already have webhook virtualAccountId and the template checklist.
2.
Build answerList: one row per question (bizType + questionId + answer / base64Files).
3.
POST the body; on success (data = true), wait for the same RFI webhook.
4.
Track review via webhook rfiStatus (SUBMITTED → APPROVED / REJECTED / RESUBMIT_REQUIRED).
5.
If RESUBMIT_REQUIRED, reload the template and call this API again.
Critical fields#
| Field | Required | Value / rule |
|---|
accountId | Yes | Webhook data.virtualAccountId |
answerList[].bizType | Yes | Template bizType / webhook awaiting tier |
answerList[].questionId | Yes | Template questions[].id (exact) |
answerList[].base64Files / file | For FILE questions | Provide at least one: fresh Base64 upload and/or existing file key |
answerList[].answer | For text questions | Required when template marks the question required |
answerList or customAnswerList | At least one | Must not both be empty |
Example request body#
{
"accountId": "4059",
"answerList": [
{
"bizType": "SUB_ACCOUNT_RFI_TIER_1",
"questionId": "RFI_TIER1_Individual_Identity_Document",
"answer": "",
"file": "",
"base64Files": [
"JVBERi0xLjQKJcfs...",
"iVBORw0KGgoAAAANSUhEUgAA..."
]
},
{
"bizType": "SUB_ACCOUNT_RFI_TIER_1",
"questionId": "RFI_TIER1_Individual_Address_Proof",
"answer": "",
"file": "",
"base64Files": [
"JVBERi0xLjQKJcfs...",
"iVBORw0KGgoAAAANSUhEUgAA..."
]
},
{
"bizType": "SUB_ACCOUNT_RFI_TIER_1",
"questionId": "RFI_TIER1_Individual_Business_Proof",
"answer": "",
"file": "",
"base64Files": [
"JVBERi0xLjQKJcfs...",
"iVBORw0KGgoAAAANSUhEUgAA..."
]
},
{
"bizType": "SUB_ACCOUNT_RFI_TIER_1",
"questionId": "RFI_TIER1_Individual_Supporting_Documents",
"answer": "",
"file": "",
"base64Files": [
"JVBERi0xLjQKJcfs...",
"iVBORw0KGgoAAAANSUhEUgAA..."
]
},
{
"bizType": "SUB_ACCOUNT_RFI_TIER_1",
"questionId": "RFI_TIER1_Individual_Additional_Information",
"answer": "",
"file": ""
}
]
}
Success response (data)#
true means the submission is accepted. Later review status comes from webhook Sub Virtual Account RFI Required via data.rfiStatus — do not treat this boolean as the final compliance result.
6. End-to-end checklist#
Configure your Stable webhook URL to accept eventType = SUB_VIRTUAL_ACCOUNT_RFI_REQUIRED.
On receive: persist eventId for idempotency; read data.virtualAccountId, awaitingRfiTypeList, and rfiStatus.
Call GET /v1/virtual-account/sub/rfi-template with bizTypeList = awaitingRfiTypeList and virtualAccountId = virtualAccountId.
Build answerList from template questions (bizType + questionId + answer / base64Files).
Call POST /v1/virtual-account/sub/submit-rfi with accountId = virtualAccountId.
Treat later webhooks of the same event type as status updates; branch on data.rfiStatus.