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

# Settlement Notification Webhook

> Real-time notifications when a settlement batch is completed, or when an individual settled transaction is refunded or has its refund cancelled. Keep your books in sync with what SingaPay has actually paid out.

<Note>
  **Accounting Operation** — This webhook fires on settlement lifecycle events (completion, refund, refund cancellation). It reports money movements between your `pending_balance`, `available_balance`, and your 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 `settlement_notif_url` whenever a settlement batch reaches a final state, or when a settled transaction is refunded / has its refund cancelled.

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

***

## Events

This single webhook covers three settlement events, distinguished by the `event` field:

<div className="overflow-x-auto">
  | `event`                       | Fired when                                                                                                                                                    | `data` contains                    |
  | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
  | `settlement.completed`        | A settlement batch is completed — either a sequential settlement is **approved** by Finance, or a **parallel (auto) settlement** is created by the scheduler. | `settlement`, `total_transactions` |
  | `settlement.refunded`         | A single settled transaction is **refunded** back from your balance (settlement method `balance` / `auto-balance` only).                                      | `settlement`, `refund`             |
  | `settlement.refund_cancelled` | A previous refund is **cancelled** and the balance is restored.                                                                                               | `settlement`, `refund`             |
</div>

<Note>
  Settlement currently supports **money-in** transactions only (Virtual Account, QRIS, Payment Link, E-Wallet). Refund events apply only to settlements using the `balance` or `auto-balance` method.
</Note>

***

## 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 <random_token>`   | Alphanumeric |    Yes    | System-generated random bearer token; used as a component in the signature |

<Note>
  The `Authorization` token for settlement webhooks is a **randomly generated string** — not a user access token. Settlement events are triggered by an admin approval, a scheduler, or a refund action (system events), not a user request. Extract the token as-is and use it in the string to sign. See [How to Validate Signature](#how-to-validate-signature) below.
</Note>

***

### Body Parameters — common envelope

Every settlement webhook shares the same top-level envelope:

<ParamField body="status" type="number" required>
  HTTP Status Code. Always `200`.
</ParamField>

<ParamField body="success" type="boolean" required>
  Indicates a successful event. Always `true`.
</ParamField>

<ParamField body="event" type="string" required>
  Event type identifier. One of `"settlement.completed"`, `"settlement.refunded"`, `"settlement.refund_cancelled"`.
</ParamField>

<ParamField body="timestamp" type="string" required>
  Event timestamp in format `"d M Y H:i:s"`. Example: `"18 Jun 2026 10:00:00"`
</ParamField>

<ParamField body="data" type="object" required>
  Container for the settlement details (and, for refund events, the refund details).
</ParamField>

***

### `data.settlement` (present on all events)

<ParamField body="settlement" type="object" required>
  The settlement batch the event relates to.

  <Expandable title="settlement fields">
    <ParamField body="id" type="integer" required>
      Unique settlement ID. Example: `1234`
    </ParamField>

    <ParamField body="reference_no" type="string" required>
      Settlement reference number. Format `SETTLEMENT-{merchant_id}-{uniqid}`. Example: `"SETTLEMENT-1-ABC123"`
    </ParamField>

    <ParamField body="title" type="string" required>
      Human-readable batch title. Example: `"Settlement Acme (01 Jun 2026 - 17 Jun 2026)"`
    </ParamField>

    <ParamField body="status" type="string" required>
      Settlement status. `"completed"` for completed settlements.
    </ParamField>

    <ParamField body="settlement_type" type="string" required>
      Transaction scope of the batch. One of `"ALL"`, `"VA"`, `"QRIS"`, `"EWALLET"`.
    </ParamField>

    <ParamField body="settlement_method" type="string" required>
      How funds were moved. One of `"balance"`, `"bank-account"`, `"auto-balance"`.
    </ParamField>

    <ParamField body="is_auto_created" type="boolean" required>
      `true` if this batch was created automatically by parallel (auto) settlement; `false` for sequential settlements approved by Finance.
    </ParamField>

    <ParamField body="start_date" type="string" required>
      Start of the transaction date range covered. Format `"d M Y H:i:s"`.
    </ParamField>

    <ParamField body="end_date" type="string" required>
      End of the transaction date range covered. Format `"d M Y H:i:s"`.
    </ParamField>

    <ParamField body="amount" type="number" required>
      Total **net** amount of the settled transactions. Example: `1000000`
    </ParamField>

    <ParamField body="total_admin_fee" type="number" required>
      Sum of admin fees across the batch. Example: `5000`
    </ParamField>

    <ParamField body="total_vendor_fee" type="number" required>
      Sum of payment-vendor fees across the batch. Example: `3000`
    </ParamField>

    <ParamField body="total_our_margin" type="number" required>
      Sum of SingaPay margin across the batch. Example: `2000`
    </ParamField>

    <ParamField body="settlement_fee" type="number" required>
      Settlement transfer fee. Non-zero only for the `bank-account` method; `0` for `balance` / `auto-balance`.
    </ParamField>

    <ParamField body="total_to_transfer" type="number" required>
      Net amount transferred: `amount - settlement_fee`. Example: `1000000`
    </ParamField>

    <ParamField body="total_refunded" type="number" required>
      Accumulated net amount that has been refunded from this settlement. Example: `0`
    </ParamField>

    <ParamField body="currency" type="string" required>
      ISO 4217 currency code. Always `"IDR"`.
    </ParamField>

    <ParamField body="transfer_status" type="string">
      Bank transfer status for the `bank-account` method: `"pending"`, `"success"`, or `"failed"`. `null` for `balance` / `auto-balance` methods.
    </ParamField>

    <ParamField body="approved_by" type="string">
      Who completed the settlement. The approver's name for sequential settlements, or `"SYSTEM"` for auto settlements.
    </ParamField>

    <ParamField body="approved_at" type="string">
      Timestamp the settlement was completed. Format `"d M Y H:i:s"`.
    </ParamField>

    <ParamField body="recipient" type="object" required>
      Destination bank account (populated only for the `bank-account` method; otherwise all fields are `null`).

      <Expandable title="recipient fields">
        <ParamField body="bank_code" type="string">Recipient bank code. Example: `"BRI"`</ParamField>
        <ParamField body="account_number" type="string">Recipient account number.</ParamField>
        <ParamField body="account_name" type="string">Recipient account holder name.</ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

***

### `data.total_transactions` (`settlement.completed` only)

<ParamField body="total_transactions" type="integer" required>
  Number of transactions included in the completed settlement batch. Example: `5`
</ParamField>

***

### `data.refund` (`settlement.refunded` & `settlement.refund_cancelled` only)

<ParamField body="refund" type="object" required>
  Details of the individual transaction that was refunded or had its refund cancelled.

  <Expandable title="refund fields">
    <ParamField body="settlement_detail_id" type="integer" required>
      ID of the settlement detail row (the transaction's line within the batch). Example: `987`
    </ParamField>

    <ParamField body="transaction_type" type="string" required>
      Class name of the underlying transaction. One of `"VATransaction"`, `"QrisTransaction"`, `"PaymentLinkHistory"`, `"EwalletTransaction"`, `"PaymentLinkingTransaction"`.
    </ParamField>

    <ParamField body="transaction_id" type="integer" required>
      ID of the underlying transaction. Example: `42`
    </ParamField>

    <ParamField body="transaction_reff" type="string" required>
      Reference of the underlying transaction. Example: `"INV-2026-001"`
    </ParamField>

    <ParamField body="account_id" type="integer">
      Account the balance movement applied to. May be `null`.
    </ParamField>

    <ParamField body="net_amount" type="object" required>
      Net amount deducted (refund) or restored (refund cancelled).

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

    <ParamField body="is_refunded" type="boolean" required>
      `true` once the detail has been refunded. Remains `true` even after a refund is cancelled.
    </ParamField>

    <ParamField body="is_refund_cancelled" type="boolean" required>
      `true` if the refund has been cancelled. `false` for an active refund.
    </ParamField>

    <ParamField body="refunded_by" type="string">
      Admin who performed the refund. May be `null`.
    </ParamField>

    <ParamField body="refunded_at" type="string">
      Timestamp the refund was performed. Format `"d M Y H:i:s"`. May be `null`.
    </ParamField>

    <ParamField body="refund_cancelled_by" type="string">
      Admin who cancelled the refund. `null` for `settlement.refunded`.
    </ParamField>

    <ParamField body="refund_cancelled_at" type="string">
      Timestamp the refund was cancelled. Format `"d M Y H:i:s"`. `null` for `settlement.refunded`.
    </ParamField>

    <ParamField body="actor" type="string">
      The admin who triggered this specific event (refunder or canceller). May be `null`.
    </ParamField>
  </Expandable>
</ParamField>

***

### Payload Examples

```json settlement.completed theme={null}
{
  "status": 200,
  "success": true,
  "event": "settlement.completed",
  "timestamp": "18 Jun 2026 10:00:00",
  "data": {
    "settlement": {
      "id": 1234,
      "reference_no": "SETTLEMENT-1-ABC123",
      "title": "Settlement Acme (01 Jun 2026 - 17 Jun 2026)",
      "status": "completed",
      "settlement_type": "ALL",
      "settlement_method": "balance",
      "is_auto_created": false,
      "start_date": "01 Jun 2026 00:00:00",
      "end_date": "17 Jun 2026 23:59:59",
      "amount": 1000000,
      "total_admin_fee": 5000,
      "total_vendor_fee": 3000,
      "total_our_margin": 2000,
      "settlement_fee": 0,
      "total_to_transfer": 1000000,
      "total_refunded": 0,
      "currency": "IDR",
      "transfer_status": null,
      "approved_by": "Jane Finance",
      "approved_at": "18 Jun 2026 10:00:00",
      "recipient": {
        "bank_code": null,
        "account_number": null,
        "account_name": null
      }
    },
    "total_transactions": 5
  }
}
```

```json settlement.completed (bank-account) theme={null}
{
  "status": 200,
  "success": true,
  "event": "settlement.completed",
  "timestamp": "18 Jun 2026 10:05:00",
  "data": {
    "settlement": {
      "id": 1240,
      "reference_no": "SETTLEMENT-1-XYZ789",
      "title": "Settlement Acme (17 Jun 2026 - 17 Jun 2026)",
      "status": "completed",
      "settlement_type": "VA",
      "settlement_method": "bank-account",
      "is_auto_created": false,
      "start_date": "17 Jun 2026 00:00:00",
      "end_date": "17 Jun 2026 23:59:59",
      "amount": 2000000,
      "total_admin_fee": 10000,
      "total_vendor_fee": 6000,
      "total_our_margin": 4000,
      "settlement_fee": 13000,
      "total_to_transfer": 1987000,
      "total_refunded": 0,
      "currency": "IDR",
      "transfer_status": "success",
      "approved_by": "Jane Finance",
      "approved_at": "18 Jun 2026 10:05:00",
      "recipient": {
        "bank_code": "BRI",
        "account_number": "1234567890",
        "account_name": "PT Acme Indonesia"
      }
    },
    "total_transactions": 8
  }
}
```

```json settlement.refunded theme={null}
{
  "status": 200,
  "success": true,
  "event": "settlement.refunded",
  "timestamp": "19 Jun 2026 09:30:00",
  "data": {
    "settlement": {
      "id": 1234,
      "reference_no": "SETTLEMENT-1-ABC123",
      "title": "Settlement Acme (01 Jun 2026 - 17 Jun 2026)",
      "status": "completed",
      "settlement_type": "ALL",
      "settlement_method": "balance",
      "is_auto_created": false,
      "start_date": "01 Jun 2026 00:00:00",
      "end_date": "17 Jun 2026 23:59:59",
      "amount": 1000000,
      "total_admin_fee": 5000,
      "total_vendor_fee": 3000,
      "total_our_margin": 2000,
      "settlement_fee": 0,
      "total_to_transfer": 1000000,
      "total_refunded": 95000,
      "currency": "IDR",
      "transfer_status": null,
      "approved_by": "Jane Finance",
      "approved_at": "18 Jun 2026 10:00:00",
      "recipient": {
        "bank_code": null,
        "account_number": null,
        "account_name": null
      }
    },
    "refund": {
      "settlement_detail_id": 987,
      "transaction_type": "VATransaction",
      "transaction_id": 42,
      "transaction_reff": "INV-2026-001",
      "account_id": 7,
      "net_amount": {
        "value": 95000,
        "currency": "IDR"
      },
      "is_refunded": true,
      "is_refund_cancelled": false,
      "refunded_by": "Bob Admin",
      "refunded_at": "19 Jun 2026 09:30:00",
      "refund_cancelled_by": null,
      "refund_cancelled_at": null,
      "actor": "Bob Admin"
    }
  }
}
```

```json settlement.refund_cancelled theme={null}
{
  "status": 200,
  "success": true,
  "event": "settlement.refund_cancelled",
  "timestamp": "19 Jun 2026 11:00:00",
  "data": {
    "settlement": {
      "id": 1234,
      "reference_no": "SETTLEMENT-1-ABC123",
      "title": "Settlement Acme (01 Jun 2026 - 17 Jun 2026)",
      "status": "completed",
      "settlement_type": "ALL",
      "settlement_method": "balance",
      "is_auto_created": false,
      "start_date": "01 Jun 2026 00:00:00",
      "end_date": "17 Jun 2026 23:59:59",
      "amount": 1000000,
      "total_admin_fee": 5000,
      "total_vendor_fee": 3000,
      "total_our_margin": 2000,
      "settlement_fee": 0,
      "total_to_transfer": 1000000,
      "total_refunded": 0,
      "currency": "IDR",
      "transfer_status": null,
      "approved_by": "Jane Finance",
      "approved_at": "18 Jun 2026 10:00:00",
      "recipient": {
        "bank_code": null,
        "account_number": null,
        "account_name": null
      }
    },
    "refund": {
      "settlement_detail_id": 987,
      "transaction_type": "VATransaction",
      "transaction_id": 42,
      "transaction_reff": "INV-2026-001",
      "account_id": 7,
      "net_amount": {
        "value": 95000,
        "currency": "IDR"
      },
      "is_refunded": true,
      "is_refund_cancelled": true,
      "refunded_by": "Bob Admin",
      "refunded_at": "19 Jun 2026 09:30:00",
      "refund_cancelled_by": "Bob Admin",
      "refund_cancelled_at": "19 Jun 2026 11:00:00",
      "actor": "Bob Admin"
    }
  }
}
```

***

## 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 `settlement_notif_url` path when building `StringToSign`.

Handle duplicate deliveries idempotently using stable identifiers from the payload — for `settlement.completed` use `data.settlement.reference_no`; for refund events combine `data.settlement.reference_no` with `data.refund.settlement_detail_id` (and the `event`).

***

## Settlement Specific Notes

<AccordionGroup>
  <Accordion title="One callback URL, three events" icon="diagram-project">
    All three events are delivered to the same `settlement_notif_url`. Always branch on the `event` field. A `settlement.completed` payload has `total_transactions`; refund events have a `refund` object instead.
  </Accordion>

  <Accordion title="Sequential vs auto settlement" icon="robot">
    `is_auto_created = false` means a sequential settlement approved by Finance (`approved_by` is the approver's name). `is_auto_created = true` means a parallel auto settlement created by the scheduler (`approved_by = "SYSTEM"`). Both arrive as `settlement.completed`.
  </Accordion>

  <Accordion title="amount vs total_to_transfer" icon="circle-dollar-to-slot">
    `amount` is the total net amount of the settled transactions. `total_to_transfer` is what was actually moved out — `amount - settlement_fee`. The `settlement_fee` is only non-zero for the `bank-account` method.
  </Accordion>

  <Accordion title="Refunds only for balance methods" icon="rotate-left">
    `settlement.refunded` and `settlement.refund_cancelled` fire only for settlements using the `balance` or `auto-balance` method. Refund deducts (and cancel restores) the transaction's **net amount** from your `available_balance` — fees are not refunded.
  </Accordion>

  <Accordion title="Refund state via flags" icon="toggle-on">
    Track refund state with `is_refunded` and `is_refund_cancelled`. An active refund is `is_refunded=true, is_refund_cancelled=false`. A cancelled refund is `is_refunded=true, is_refund_cancelled=true` — note `is_refunded` stays `true`.
  </Accordion>

  <Accordion title="Nullable fields" icon="triangle-exclamation">
    `transfer_status` and the `recipient.*` fields are `null` for `balance` / `auto-balance` settlements. Actor fields (`refunded_by`, `refund_cancelled_by`, `actor`) and `account_id` may be `null`. Handle `null` gracefully.
  </Accordion>

  <Accordion title="Timestamp format & timezone" icon="clock">
    All timestamps (`timestamp`, `approved_at`, `start_date`, `end_date`, `refunded_at`, `refund_cancelled_at`) use the format `"d M Y H:i:s"` (e.g. `"18 Jun 2026 10:00:00"`) in **Asia/Jakarta (WIB)** timezone — not UTC.
  </Accordion>
</AccordionGroup>
