> ## Documentation Index
> Fetch the complete documentation index at: https://docs.singapay.id/llms.txt
> Use this file to discover all available pages before exploring further.

# Overview

> Pull funds directly from a customer's bank account using a one-time authorization binding — no card required.

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.

<Info>
  This is a **Money In** operation. Each charge pulls funds from the customer's bound bank account into your Singapay merchant settlement account.
</Info>

<CardGroup cols={3}>
  <Card title="One-time setup" icon="link">
    Customer authorizes their bank account once via a hosted webview. All future charges use the stored `binding_id` — no customer redirect needed.
  </Card>

  <Card title="OTP-aware charging" icon="shield-check">
    Some banks require an OTP per charge. SingaPay handles both flows automatically and signals when `/verify-otp` is needed.
  </Card>

  <Card title="Multi-bank support" icon="building-columns">
    Supports BRI, Mandiri, BNI, Danamon, BCA, CIMB, BNC, and more via BI/SNAP bank codes.
  </Card>
</CardGroup>

***

## 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

```mermaid theme={null}
sequenceDiagram
    participant C as Customer
    participant M as Merchant System
    participant S as SingaPay API
    participant B as Bank

    Note over C,B: Phase 1 — Binding (one-time setup)
    M->>S: POST /direct-debit/bindings
    Note over M,S: customer_ref, phone_no, bank_code, payment_otp_mode
    S-->>M: binding_id, redirect_url, expires_at (status: PENDING_AUTH)

    M->>C: Redirect to redirect_url
    C->>B: Authorize via hosted webview
    B-->>S: Binding result callback
    S->>S: Binding status → ACTIVE / FAILED / EXPIRED
    S-->>M: Webhook: binding.succeeded / binding.failed

    Note over C,B: Phase 2 — Charge (repeatable, no customer redirect)
    M->>S: POST /direct-debit/charge
    Note over M,S: binding_id, amount, merchant_reference

    alt No OTP required
        S->>B: Debit bank account
        B-->>S: Settlement confirmation
        S->>S: Transaction status → SUCCESS
        S-->>M: Webhook: transaction SUCCESS
    else OTP required
        S-->>M: HTTP 202 — requires_otp: true, transaction_id
        B->>C: OTP delivered by bank
        C->>M: Submit OTP
        M->>S: POST /direct-debit/verify-otp
        Note over M,S: transaction_id + otp
        S->>B: Verify OTP & debit account
        B-->>S: Settlement confirmation
        S->>S: Transaction status → SUCCESS / FAILED
        S-->>M: Webhook: transaction SUCCESS / FAILED
    end
```

***

## Binding Status

| Status         | Description                                                                             |
| -------------- | --------------------------------------------------------------------------------------- |
| `PENDING_AUTH` | Binding initiated — customer has been redirected to the bank webview for authorization. |
| `ACTIVE`       | Customer authorized successfully. This binding can be used for charges.                 |
| `UNBOUND`      | Binding was revoked by the merchant via the Unbind endpoint.                            |
| `FAILED`       | Customer cancelled or the bank rejected the authorization.                              |
| `EXPIRED`      | Customer did not complete authorization before `expires_at`.                            |

***

## Transaction Status

| Status        | Description                                                                       |
| ------------- | --------------------------------------------------------------------------------- |
| `PENDING`     | Charge accepted, awaiting settlement confirmation from the bank.                  |
| `PENDING_OTP` | OTP required — waiting for customer to submit OTP via `/verify-otp`.              |
| `SUCCESS`     | Funds credited to merchant settlement account. Fee breakdown available in `fees`. |
| `FAILED`      | Bank rejected the charge — check `failure_code` and `failure_reason`.             |
| `EXPIRED`     | OTP not submitted in time, or upstream timeout.                                   |

***

## Recommended Flow

<Steps>
  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Confirm payment outcome">
    Listen for the transaction webhook or call **Get Transaction** by `transaction_id` to confirm the final `SUCCESS` or `FAILED` status.
  </Step>
</Steps>

***

## Supported Banks

| Bank                        | Bank Code |
| --------------------------- | --------- |
| Bank Rakyat Indonesia (BRI) | `002`     |
| Bank Mandiri                | `008`     |
| Bank Negara Indonesia (BNI) | `009`     |
| Bank Danamon                | `011`     |
| Bank Central Asia (BCA)     | `014`     |
| CIMB Niaga                  | `022`     |
| Bank Neo Commerce (BNC)     | `490`     |

***

## Important Notes

<AccordionGroup>
  <Accordion title="Redirect URL is single-use and short-lived" icon="link">
    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.
  </Accordion>

  <Accordion title="OTP mode is bank-determined" icon="shield-check">
    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.
  </Accordion>

  <Accordion title="Idempotency via merchant_reference" icon="fingerprint">
    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.
  </Accordion>

  <Accordion title="Unbinding may also require OTP" icon="rotate-left">
    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.
  </Accordion>

  <Accordion title="Signature required for charge" icon="lock">
    The **Charge** endpoint requires additional `X-Signature` and `X-Timestamp` headers — separate from the standard Bearer token. See [Authentication](/api-reference/authentication) for signing requirements.
  </Accordion>

  <Accordion title="IP whitelisting required" icon="server">
    All Direct Debit endpoints require IP whitelisting. Ensure your server IP is registered under the merchant account before going live.
  </Accordion>
</AccordionGroup>

***

## Available Endpoints

<CardGroup cols={3}>
  <Card title="Binding Card" icon="link" href="./binding-card">
    Initiate a binding — returns a hosted webview `redirect_url` for the customer to authorize their bank account.
  </Card>

  <Card title="Binding Status" icon="eye" href="./binding-status">
    Check the current state of a binding by `binding_id`.
  </Card>

  <Card title="Unbind Card" icon="link-slash" href="./unbind-card">
    Revoke an active binding. May require OTP verification for some banks.
  </Card>

  <Card title="Charge" icon="bolt" href="./charge">
    Pull funds from a bound bank account. Returns `requires_otp: true` when OTP verification is needed.
  </Card>

  <Card title="Verify OTP" icon="key" href="./verify-otp">
    Submit the customer OTP to complete a charge or unbinding that requires verification.
  </Card>

  <Card title="Get Transaction" icon="magnifying-glass" href="./get-transaction">
    Retrieve the latest status of a Direct Debit charge by `transaction_id`.
  </Card>
</CardGroup>

<Note>
  All endpoints require authentication. The Charge endpoint additionally requires `X-Signature` and `X-Timestamp`. See [Authentication](/api-reference/authentication).
</Note>
