PIK pushes a webhook to your endpoint when a virtual account (VA) you requested becomes ready to receive funds. Use this event to surface the new account details (account number, bank name, routing key, etc.) to your end user, or to mark the VA as active in your own system.This document covers everything needed to integrate the Virtual Account webhook end-to-end: delivery format, signature verification, retry behaviour, the event lifecycle, and a JSON sample.
1. How delivery works#
| Item | Value |
|---|
| HTTP method | POST |
| Content type | application/json; charset=utf-8 |
| Request timeout | 10 seconds |
| Success rule | HTTP 2xx is treated as ack; anything else is a failure |
| Retry policy | Up to 5 retries, 5 minutes between attempts |
| Header | Description |
|---|
Content-Type | Always application/json. |
X-Webhook-Event | Event group. For this document the value is always VIRTUAL. |
X-Webhook-Event-Type | Concrete event type. Currently only virtual.account.update is forwarded to clients. |
X-Webhook-Signature | Hex-encoded HMAC-SHA256 of the raw request body using your App Secret. Present only when an App Secret is configured for your account. |
Signature verification#
The signature is computed as:X-Webhook-Signature = HEX( HMAC_SHA256( app_secret, raw_request_body ) )
Sign the raw request body bytes (UTF-8), not a re-serialized JSON, and verify in constant time.
Client response requirements#
Reply with HTTP 200 (or any 2xx) as soon as you have persisted the event.
Any non-2xx response, network error, or timeout will be retried.
Keep ack bodies small (e.g. {"received":true}) — we only log the first 1000 chars of the response.
Retry behaviour#
| Attempt | Trigger |
|---|
| 1 | Immediately when PIK processes the source event |
| 2–6 | 5 minutes after the previous failure |
| Final | After 5 retries the task is marked EXHAUSTED and is not sent again |
Idempotency#
VA provisioning may produce duplicate deliveries during retries. Use event_id as the idempotency key in your handler.For business identity across events for the same VA, use data.account_bank_id (stable across the lifetime of the VA).
2. Envelope#
{
"version": "V1.6.0",
"event_name": "VIRTUAL",
"event_type": "virtual.account.update",
"event_id": "<uuid, unique per event delivery>",
"source_id": "<account_bank_id this event refers to>",
"data": { ... }
}
| Field | Type | Description |
|---|
version | string | Webhook payload schema version. |
event_name | string | Event group. Always VIRTUAL. |
event_type | string | Concrete event. See §3. |
event_id | string | Unique ID of this event delivery. Use as idempotency key. |
source_id | string | The business object the event is about. For VAs this is account_bank_id. |
data | object | Event-specific payload. See §5. |
3. Event types#
event_type | Meaning | Forwarded to clients? |
|---|
virtual.account.create | Internal — the VA creation request has been accepted and provisioning has started. Not forwarded to clients. | No |
virtual.account.update | The VA has been fully provisioned and is active. Full account details (account number, bank, routing) are now populated. | Yes |
Only virtual.account.update (Active) is delivered to your endpoint. The earlier virtual.account.create (Processing) phase is handled internally by PIK; you do not need to react to it.
4. State machine#
VA created via API
(local status = Creating)
│
│ PIK starts provisioning the account
▼
┌──────────────────────────────────────┐
│ virtual.account.create │ internal only
│ (local status = Processing) │ ── NOT forwarded ──
│ account_id assigned, provisioning │
└──────────────────┬───────────────────┘
│
│ provisioning completes
▼
┌──────────────────────────────────────┐
│ virtual.account.update │ ✓ forwarded to your endpoint
│ (local status = Active) │
│ account_number, bank, routing are │
│ now populated and usable │
└──────────────────────────────────────┘
(terminal)
Treat virtual.account.update as the green-light signal that the VA can receive funds. Until you see it, the account is not usable.
If the VA is later closed, data.close_reason will be populated. The event_type for closure is reserved for a future version and is not yet emitted.
5. data field reference#
| Field | Type | Always present? | Description |
|---|
request_id | string | yes | The client request UUID supplied when the VA was created. Use to correlate with your own VA-creation record. |
direct_id | string | yes | Parent direct account identifier. "0" if not applicable. |
account_id | string | yes | The owning sub-account identifier on PIK. |
account_bank_id | string | yes | The VA's unique identifier. Stable for the lifetime of the VA — use as VA primary key in your system. |
account_holder | string | yes | The legal holder name on the VA. |
account_number | string | yes | The bank account number assigned to the VA. |
country_code | string | yes | ISO 3166-1 alpha-2 country code (e.g. SG, AU). |
currency | string | yes | ISO 4217 currency code (e.g. USD, SGD). |
bank_name | string | yes | Human-readable bank name. |
bank_address | string | optional | Bank postal address, if available. |
clearing_system.type | string | yes | Routing key type, e.g. SWIFT, BSB, IBAN, LOCAL. |
clearing_system.value | string | yes | Routing key value matching the type above. |
capability.payment_method | string | optional | Supported payment rails on this VA (e.g. LOCAL,SWIFT). |
close_reason | string | optional | Populated only when the VA has been closed. |
6. Sample payload — virtual.account.update#
{
"version": "V1.6.0",
"event_name": "VIRTUAL",
"event_type": "virtual.account.update",
"event_id": "a2b8e3d4-91fe-4d65-b41b-08e6c1f4d771",
"source_id": "abk_3f9d0a51e2bc4a7c",
"data": {
"request_id": "8c2f33ee-7a91-4f9a-9e8e-0a91e0d6f200",
"direct_id": "0",
"account_id": "ac1e31ab-f0fd-4432-91fb-b06ec1b3d7b9",
"account_bank_id": "abk_3f9d0a51e2bc4a7c",
"account_holder": "ACME PTE LTD.",
"account_number": "1234567890",
"country_code": "SG",
"currency": "SGD",
"bank_name": "DBS Bank Ltd.",
"bank_address": "12 Marina Boulevard, Singapore 018982",
"clearing_system": {
"type": "LOCAL",
"value": "7171"
},
"capability": {
"payment_method": "LOCAL,SWIFT"
},
"close_reason": null
}
}
Modified at 2026-05-25 08:15:33