Skip to main content
Direct Debit lets you debit funds directly from a customer’s bank account after a one-time authorization setup. The customer links their bank account once through a hosted webview, and you can charge it on demand — without redirecting the customer again.
This is a Money In operation. Each charge pulls funds from the customer’s bound bank account into your Singapay merchant settlement account.

One-time setup

Customer authorizes their bank account once via a hosted webview. All future charges use the stored binding_id — no customer redirect needed.

OTP-aware charging

Some banks require an OTP per charge. SingaPay handles both flows automatically and signals when /verify-otp is needed.

Multi-bank support

Supports BRI, Mandiri, BNI, Danamon, BCA, CIMB, BNC, and more via BI/SNAP bank codes.

How Direct Debit Works

Direct Debit has two phases: Binding (one-time) and Charge (repeatable).
  • Binding — The customer authorizes their bank account via a hosted webview. On success, an ACTIVE binding is created and can be used for future charges indefinitely.
  • Charge — The merchant initiates a pull payment using the binding_id. Depending on the bank’s payment_otp_mode, the charge either completes silently or requires the customer to submit an OTP.

Flow Diagram


Binding Status

StatusDescription
PENDING_AUTHBinding initiated — customer has been redirected to the bank webview for authorization.
ACTIVECustomer authorized successfully. This binding can be used for charges.
UNBOUNDBinding was revoked by the merchant via the Unbind endpoint.
FAILEDCustomer cancelled or the bank rejected the authorization.
EXPIREDCustomer did not complete authorization before expires_at.

Transaction Status

StatusDescription
PENDINGCharge accepted, awaiting settlement confirmation from the bank.
PENDING_OTPOTP required — waiting for customer to submit OTP via /verify-otp.
SUCCESSFunds credited to merchant settlement account. Fee breakdown available in fees.
FAILEDBank rejected the charge — check failure_code and failure_reason.
EXPIREDOTP not submitted in time, or upstream timeout.

1

Create a binding

Call Binding Card with the customer’s phone number, bank code, and preferred OTP mode. Redirect the customer to the returned redirect_url to complete bank authorization.
2

Confirm binding is active

Listen for the direct-debit.binding.succeeded webhook, or poll Binding Status until the status is ACTIVE. Store the binding_id — you will use it for all future charges.
3

Charge the bound account

Call Charge with the binding_id, amount, and a unique merchant_reference. If the bank requires OTP (requires_otp: true), proceed to the next step.
4

Verify OTP (if required)

If the Charge response returns HTTP 202 with requires_otp: true, prompt the customer to enter the OTP sent by their bank, then call Verify OTP with the transaction_id and OTP value.
5

Confirm payment outcome

Listen for the transaction webhook or call Get Transaction by transaction_id to confirm the final SUCCESS or FAILED status.

Supported Banks

BankBank Code
Bank Rakyat Indonesia (BRI)002
Bank Mandiri008
Bank Negara Indonesia (BNI)009
Bank Danamon011
Bank Central Asia (BCA)014
CIMB Niaga022
Bank Neo Commerce (BNC)490

Important Notes

The redirect_url returned on binding creation is single-use and expires at expires_at (typically ~15 minutes). Treat it as opaque — do not parse or hardcode its host or path. If it expires before the customer completes authorization, create a new binding.
The payment_otp_mode parameter is a preferred hint — the bank may require OTP regardless of this setting. Always check the Charge response for requires_otp before assuming a silent charge will complete.
The merchant_reference field acts as an idempotency key for charges. Submitting the same reference twice returns SP_DD_DUPLICATE_REFERENCE — store the first transaction_id and query its status instead of retrying with a new request.
Some banks require OTP verification when revoking a binding. If the Unbind response returns otp_required: true, call Verify OTP with the binding_id and the unbind_context from the otp_handoff block.
The Charge endpoint requires additional X-Signature and X-Timestamp headers — separate from the standard Bearer token. See Authentication for signing requirements.
All Direct Debit endpoints require IP whitelisting. Ensure your server IP is registered under the merchant account before going live.

Available Endpoints

Binding Card

Initiate a binding — returns a hosted webview redirect_url for the customer to authorize their bank account.

Binding Status

Check the current state of a binding by binding_id.

Unbind Card

Revoke an active binding. May require OTP verification for some banks.

Charge

Pull funds from a bound bank account. Returns requires_otp: true when OTP verification is needed.

Verify OTP

Submit the customer OTP to complete a charge or unbinding that requires verification.

Get Transaction

Retrieve the latest status of a Direct Debit charge by transaction_id.
All endpoints require authentication. The Charge endpoint additionally requires X-Signature and X-Timestamp. See Authentication.