Skip to main content
Sub-Account Operation — This webhook fires on KYB review outcomes for your managed sub-accounts (business_managed). It does not apply to your own merchant-level KYB, or to owned/personal_managed sub-accounts (which never require KYB).

Information

SingaPay sends a POST request to your configured kyb_notif_url whenever a managed sub-account’s KYB submission finishes review — either approved, or rejected at one of the review stages.

Events

This single webhook covers two events, distinguished by the event field:
KYB review runs through three sequential stages: Document Check → DTTOT Check → Blacklist Check. kyb.rejected can fire at any of the three — you will receive a separate webhook call for each rejection, not just one at the end. kyb.approved fires only once, after the final stage passes.

Request Details

Headers

The Authorization token for KYB webhooks is a randomly generated string — not a user access token. KYB review outcomes are triggered by an internal reviewer action, not a merchant request. Extract the token as-is and use it in the string to sign. See Security and signature validation.

Body Parameters — common envelope

Every KYB webhook shares the same top-level envelope:
number
required
HTTP Status Code. Always 200.
boolean
required
Indicates a successful event. Always true.
string
required
Event type identifier. One of "kyb.approved", "kyb.rejected".
string
required
Event timestamp in format "d M Y H:i:s". Example: "28 Aug 2026 14:33:55"
object
required
Container for the account details (and, for rejections, the rejection details).

data.account (present on all events)

object
required
The sub-account the event relates to. Shape is identical to the Account object returned by GET /accounts/{id}.

data.verified_at (kyb.approved only)

string
required
Timestamp the final review stage was approved. Format "d M Y H:i:s".

data.stage, data.reason, data.rejected_at (kyb.rejected only)

string
required
Which review stage rejected the submission. One of "document", "dttot", "blacklist".
string
required
Free-text reason written by the reviewer. Not a structured error code — read it directly to determine what needs correcting.
string
required
Timestamp the rejection was recorded. Format "d M Y H:i:s".

Payload Examples

kyb.approved
kyb.rejected

Security and responses

Return HTTP 200 promptly after validating the request. For retry behavior, see Webhook retry mechanism. Verify every webhook using Security and signature validation. Use your configured kyb_notif_url path when building StringToSign. Handle duplicate deliveries idempotently using data.account.id combined with the event (and, for rejections, data.stage) as a stable identifier.

Sub-Account KYB Specific Notes

kyb_notif_url is a dedicated field — it does not share a callback URL with any other webhook type. Configure it separately in your dashboard under Settings → Credentials → Webhook & Notification URLs.
Review runs through three sequential stages. If a sub-merchant is rejected, corrects the issue, and resubmits, and is rejected again at a later stage, you will receive a new kyb.rejected webhook for that stage — each rejection in the account’s history triggers its own call.
kyb_status = "kyb_rejected" tells you a rejection is outstanding, but the specific reason lives in kyb_rejection_reason — a free-text string written by a human reviewer, not a structured per-field error code. If you need to know exactly which document or field was flagged, parse the reason text or direct your sub-merchant to read it carefully before resubmitting.
All timestamps (timestamp, verified_at, rejected_at) use the format "d M Y H:i:s" (e.g. "28 Aug 2026 14:33:55") in Asia/Jakarta (WIB) timezone — not UTC.