Skip to main content
POST
/
api
/
v2.0
/
direct-debit
/
verify-otp
Verify OTP
curl --request POST \
  --url https://sandbox-payment-b2b.singapay.id/api/v2.0/direct-debit/verify-otp \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --header 'X-PARTNER-ID: <api-key>' \
  --data '
{
  "otp": "123456",
  "transaction_id": "01HZX9JK4M5N6P7Q8R9STUVWXY",
  "binding_id": "01HZX9JK4M5N6P7Q8R9STUVWXY",
  "unbind_context": {
    "external_id": "external-id-123456",
    "partner_reference_no": "partner-ref-123456",
    "otp_token": "otp-token-abc123def456",
    "flow": "UNBINDING",
    "reference_no": "gw-ref-789012"
  }
}
'
{
  "response_code": "SP000",
  "response_message": "OTP accepted; awaiting settlement callback",
  "data": {
    "transaction_id": "7c2e1a4b-9d6f-4e3a-8b1c-2d4f9a1c5b3e",
    "binding_id": "9a1c5b3e-2d4f-4d8c-93cf-9a1c5b3e2d4f",
    "account_id": "01HZX9JK4M5N6P7Q8R9STUVWXY",
    "merchant_reference": "INV-2026-0001",
    "amount": 150000,
    "currency": "IDR",
    "status": "PENDING",
    "requires_otp": true,
    "paid_at": null,
    "failure_code": null,
    "failure_reason": null,
    "web_redirect_url": null,
    "created_at": "2026-06-04T08:30:01+07:00",
    "updated_at": "2026-06-04T08:31:31+07:00"
  }
}

Authorizations

Authorization
string
header
required

JWT issued by POST /api/v1.1/access-token/b2b. Send Authorization: Bearer <token>.

X-PARTNER-ID
string
header
required

Merchant API key (Credential.api_key). Required on every request.

Body

application/json

Submits an OTP to complete either a payment authorization or an unbind operation. Use transaction_id for payment-OTP flows, or binding_id + unbind_context for unbind-OTP flows. Exactly one of these flow definitions must be present.

otp
string
required

OTP value the customer received from their bank.

Required string length: 4 - 8
Example:

"123456"

transaction_id
string<uuid> | null

Required for the payment-OTP flow; the id returned by POST /charge when requires_otp=true.

Example:

"01HZX9JK4M5N6P7Q8R9STUVWXY"

binding_id
string<uuid> | null

Required for the unbinding-OTP flow; together with unbind_context from the original /unbind response.

Example:

"01HZX9JK4M5N6P7Q8R9STUVWXY"

unbind_context
object | null

Verbatim otp_handoff block returned by POST /bindings/{binding_id}/unbind (when otp_required=true). Required for the unbinding-OTP flow.

Response

SP000 OTP accepted. For the payment flow, data matches DirectDebitTransactionData and status moves PENDING_OTP → PENDING (final SUCCESS arrives via webhook). For the unbinding flow, data matches DirectDebitBindingData; the binding stays ACTIVE until the direct-debit.unbinding.succeeded callback flips it to UNBOUND.

SingaPay Merchant API v2 custom response envelope (ApiResponderHelper::responseJson, ApiResponseTrait). Business outcome is determined by response_code (SP000–SP020), not by HTTP status alone. On success (SP000), data holds the operation payload. On errors, data often includes a message and may echo request fields.

response_code
string
required

SingaPay custom business response code.

Example:

"SP000"

response_message
string
required

Human-readable label paired with response_code.

Example:

"Successfully"

data
object | null

Endpoint-specific payload on success, or error context (validation message, inquiry result with status invalid, etc.).