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

# Disbursement Webhook

> Real-time notifications for disbursement (money out) transactions processed through the SingaPay platform. Monitor transaction statuses and keep your system in sync.

<Note>
  **Money Out Operation** — This webhook fires when funds are transferred from your SingaPay account to a beneficiary's bank account.
</Note>

## Information

<div className="overflow-x-auto">
  | Method                                                                                                              | Path                                | Format | Authentication        |
  | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ------ | --------------------- |
  | <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold bg-blue-500 text-white">POST</span> | `https://your-webhook-url/callback` | json   | HMAC SHA512 Signature |
</div>

SingaPay sends a `POST` request to your configured `disbursement_notif_url` when a disbursement transaction is successfully processed or fails.

<Info>
  This webhook may share a callback URL with other event types. See [Shared webhook endpoints](/api-reference/webhooks/shared-endpoints) for routing by `event` value.
</Info>

***

## Request Details

### Headers

| Field           | Value                     | Type         | Mandatory | Description                                                |
| --------------- | ------------------------- | ------------ | :-------: | ---------------------------------------------------------- |
| `Content-Type`  | `application/json`        | Alphabetic   |    Yes    | Specifies JSON format for the request body                 |
| `User-Agent`    | `SingaPaymentGateway/1.0` | Alphabetic   |    Yes    | Identifies the source of the webhook                       |
| `Accept`        | `application/json`        | Alphabetic   |    Yes    | Expected response format                                   |
| `X-PARTNER-ID`  | —                         | Alphanumeric |    Yes    | Your API Key from the merchant dashboard                   |
| `X-Signature`   | —                         | Alphanumeric |    Yes    | HMAC SHA512 signature (128 chars) for request verification |
| `X-Timestamp`   | —                         | Numeric      |    Yes    | Unix timestamp in **seconds** when the request was sent    |
| `Authorization` | `Bearer <access_token>`   | Alphanumeric |    Yes    | JWT bearer token; also used as component in the signature  |

<Note>
  All signature-related headers (`X-Signature`, `X-Timestamp`, `Authorization`) are always included when your merchant account has API credentials configured. Extract the access token from the `Authorization` header and use it as-is in the string to sign. See [How to Validate Signature](#how-to-validate-signature) below.
</Note>

***

### Body Parameters

<ParamField body="response_code" type="string" required>
  Response code. See [Appendix 01 — Response Codes](#01-response-codes). Example: `SP000`
</ParamField>

<ParamField body="response_message" type="string" required>
  Human-readable response message. Example: `Successfully`
</ParamField>

<ParamField body="event" type="string" required>
  Transaction type identifier. Always `"disbursement"` for bank transfer. Use this to distinguish from `"ewallet-topup"` and `"qris-issuer"` on the shared URL.
</ParamField>

<ParamField body="data" type="object" required>
  Response payload object.

  <Expandable title="data fields">
    <ParamField body="transaction_id" type="string" required>
      System-generated transaction ID. Example: `"1512220251105174015668"`
    </ParamField>

    <ParamField body="reference_number" type="string" required>
      Your unique merchant reference number. Example: `"123456789123"`
    </ParamField>

    <ParamField body="transaction_status" type="object" required>
      Transaction status object. See [Appendix 02 — Transaction Status](#02-transaction-status).

      <Expandable title="transaction_status fields">
        <ParamField body="code" type="string" required>
          Status code. E.g. `"00"`, `"01"`, `"06"`
        </ParamField>

        <ParamField body="desc" type="string" required>
          Status description. E.g. `"Success"`, `"Initiated"`, `"Failed"`
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="post_timestamp" type="string" required>
      Transaction creation time in Unix **milliseconds**. Example: `"1762339215000"`
    </ParamField>

    <ParamField body="processed_timestamp" type="string">
      Processing completion time in Unix **milliseconds**. Empty string if the transaction failed.
    </ParamField>

    <ParamField body="bank" type="object" required>
      Beneficiary bank information.

      <Expandable title="bank fields">
        <ParamField body="code" type="string" required>
          Bank Swift or number code. E.g. `"002"`, `"BRINIDJA"`
        </ParamField>

        <ParamField body="name" type="string" required>
          Bank name. E.g. `"BRI"`, `"BNI"`, `"DANAMON"`
        </ParamField>

        <ParamField body="account_name" type="string">
          Beneficiary account holder name. `null` if the account inquiry failed.
        </ParamField>

        <ParamField body="account_number" type="string" required>
          Beneficiary bank account number. Example: `"521398319083210"`
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="gross_amount" type="object" required>
      Total amount including fees.

      <Expandable title="gross_amount fields">
        <ParamField body="currency" type="string" required>
          ISO 4217 currency code. Example: `"IDR"`
        </ParamField>

        <ParamField body="value" type="string" required>
          Amount value. Example: `"50000"`
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="fee" type="object" required>
      Transfer fee details.

      <Expandable title="fee fields">
        <ParamField body="currency" type="string" required>ISO 4217 currency code.</ParamField>
        <ParamField body="value" type="string" required>Fee amount. Example: `"3000"`</ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="net_amount" type="object" required>
      Amount received by the beneficiary (`gross_amount − fee`).

      <Expandable title="net_amount fields">
        <ParamField body="currency" type="string" required>ISO 4217 currency code.</ParamField>
        <ParamField body="value" type="string" required>Net amount value. Example: `"47000"`</ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="balance_after" type="object" required>
      Your account balance after the transaction. Both fields are `null` for failed transactions.

      <Expandable title="balance_after fields">
        <ParamField body="currency" type="string">ISO 4217 currency code. `null` if failed.</ParamField>
        <ParamField body="value" type="string">Balance value. `null` if failed. Example: `"777000"`</ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="notes" type="string">
      Transaction notes from the original request. Example: `"bayar pajak"`
    </ParamField>

    <ParamField body="failed_code" type="string">
      Error code. Only present when the transaction status is failed. Example: `"SP003"`
    </ParamField>

    <ParamField body="failed_reason" type="string">
      Human-readable error message. Only present when the transaction status is failed. Example: `"INSUFFICIENT BALANCE"`
    </ParamField>
  </Expandable>
</ParamField>

***

### Payload Examples

<CodeGroup>
  ```json Success theme={null}
  {
      "response_code": "SP000",
      "response_message": "Successfully",
      "event": "disbursement",
      "data": {
          "transaction_id": "101222025122910292195055674",
          "reference_number": "11111111118",
          "transaction_status": {
              "code": "00",
              "desc": "Success"
          },
          "post_timestamp": "1766978961000",
          "processed_timestamp": "1766978962000",
          "bank": {
              "code": "002",
              "name": "BRI",
              "account_name": "Dummy Test Account Internal",
              "account_number": "11111111118"
          },
          "gross_amount": {
              "currency": "IDR",
              "value": "12504.00"
          },
          "fee": {
              "currency": "IDR",
              "value": "2500"
          },
          "net_amount": {
              "currency": "IDR",
              "value": "10004.00"
          },
          "balance_after": {
              "currency": "IDR",
              "value": "829988"
          },
          "notes": "test transfer"
      }
  }
  ```

  ```json Failed theme={null}
  {
      "response_code": "SP001",
      "response_message": "Transaction Failure",
      "event": "disbursement",
      "data": {
          "transaction_id": "121222025122617513896515436",
          "reference_number": "333",
          "transaction_status": {
              "code": "06",
              "desc": "Failed"
          },
          "post_timestamp": "1766746298000",
          "processed_timestamp": "",
          "bank": {
              "code": "002",
              "name": "BRI",
              "account_name": "",
              "account_number": "091701064838533"
          },
          "gross_amount": {
              "currency": "IDR",
              "value": "12501.00"
          },
          "fee": {
              "currency": "IDR",
              "value": "2500"
          },
          "net_amount": {
              "currency": "IDR",
              "value": "10001.00"
          },
          "balance_after": {
              "currency": "IDR",
              "value": "0"
          },
          "notes": "test transfer",
          "failed_reason": "Transaction Failure : Invalid beneficiary account: Account inactive",
          "failed_code": "SP001"
      }
  }
  ```
</CodeGroup>

***

***

## Security and responses

Return HTTP `200` promptly after validating the request. For retry behavior, see [Webhook retry mechanism](/api-reference/webhooks/retry-mechanism).

Verify every webhook using [Security and signature validation](/api-reference/webhooks/security-and-signature). Use your configured callback path when building `StringToSign`.

Handle duplicate deliveries idempotently using stable identifiers from the payload (for example `transaction_id` or `reff_no`).

***

## Disbursement-Specific Notes

### Idempotency

Each `reference_number` can only be used once per account. If you retry with the same `reference_number`, you'll receive the existing transaction status rather than creating a duplicate transfer.

```php Detecting Duplicate Webhooks theme={null}
$referenceNumber = $payload['data']['reference_number'];

// Check if already processed
$existingTransaction = findTransactionByReference($referenceNumber);
if ($existingTransaction) {
    // Already processed — return 200 without re-processing
    http_response_code(200);
    exit;
}

processNewDisbursement($payload);
```

### Timestamp Formats

<Warning>
  Disbursement webhooks use **two different timestamp formats**. Don't mix them up.
</Warning>

| Field                        | Format            | Example         | Notes                              |
| ---------------------------- | ----------------- | --------------- | ---------------------------------- |
| `X-Timestamp` header         | Unix seconds      | `1695711945`    | Used for signature validation      |
| `post_timestamp` (body)      | Unix milliseconds | `1762339215000` | Divide by 1000 to convert          |
| `processed_timestamp` (body) | Unix milliseconds | `1762339215672` | Empty string if transaction failed |

```php Converting millisecond timestamps theme={null}
// Convert to DateTime
$postTimestamp = $payload['data']['post_timestamp']; // "1762339215000"
$date = new DateTime();
$date->setTimestamp((int)($postTimestamp / 1000));

// Or with Carbon (Laravel)
$date = \Carbon\Carbon::createFromTimestampMs($postTimestamp);
```

### Balance Reconciliation

The `balance_after` field shows your account balance after each transaction. For failed transactions, both `currency` and `value` are `null` — no balance change occurred.

```php Balance Consistency Check theme={null}
if ($payload['data']['transaction_status']['code'] === '00') {
    $balanceAfter = (float)$payload['data']['balance_after']['value'];
    $grossAmount  = (float)$payload['data']['gross_amount']['value'];

    $expectedBalance = $previousBalance - $grossAmount;
    if (abs($balanceAfter - $expectedBalance) > 0.01) {
        // Balance mismatch — log for investigation
        logBalanceDiscrepancy($transactionId, $expectedBalance, $balanceAfter);
    }
}
```

***

## Appendix

### 01: Response Codes

| Code    | Message                       | Description                       | Action               |
| ------- | ----------------------------- | --------------------------------- | -------------------- |
| `SP000` | Successfully                  | Success                           | Check inquiry status |
| `SP001` | Transaction Failure           | Failed transaction                | Check inquiry status |
| `SP002` | General Failure               | Internal server error             | Check inquiry status |
| `SP003` | Insufficient Balance          | Account balance too low           | Check inquiry status |
| `SP004` | Duplicate Reference Number    | Reference already used            | —                    |
| `SP005` | Timeout                       | Gateway timeout                   | Check inquiry status |
| `SP006` | Exceed Beneficiary Limit      | Beneficiary amount limit reached  | Check inquiry status |
| `SP007` | Exceed Account Limit          | Account transaction limit reached | Contact IT Support   |
| `SP008` | Invalid Reference Number      | Reference does not exist          | —                    |
| `SP009` | Transaction Not Found         | Transaction not found             | —                    |
| `SP010` | Beneficiary Account Not Found | Account not found                 | —                    |
| `SP011` | Beneficiary Vendor Not Active | Vendor not active                 | —                    |
| `SP012` | Bad Request                   | Bad request                       | —                    |
| `SP013` | Unauthorized                  | Unauthorized                      | —                    |
| `SP014` | Not Found                     | Not found                         | —                    |
| `SP015` | Forbidden                     | Forbidden                         | —                    |
| `SP016` | Signature Invalid             | Signature invalid                 | —                    |
| `SP017` | Unauthorized IP               | IP not authorized                 | Contact IT Support   |
| `SP018` | Validation Error              | Payload validation error          | —                    |
| `SP019` | General Error                 | General error                     | Contact IT Support   |
| `SP020` | Merchant Account Not Found    | Merchant account not found        | —                    |

### 02: Transaction Status

| Code | Status    | Notes                                                                          |
| :--: | --------- | ------------------------------------------------------------------------------ |
| `00` | Success   | Transaction completed successfully                                             |
| `01` | Initiated | Payment call not yet received; retry is possible                               |
| `02` | Paying    | Pending/Suspect — retry status check in a few minutes; awaiting final callback |
| `03` | Pending   | Pending/Suspect — retry status check in a few minutes; awaiting final callback |
| `04` | Refunded  | Reversal transaction                                                           |
| `05` | Canceled  | Create a new transaction                                                       |
| `06` | Failed    | Transaction failed                                                             |
| `07` | Not Found | Create a new transaction                                                       |
