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

# E-Wallet Top Up Webhook

> Real-time notifications when an E-Wallet Top Up (money out) transaction completes or fails. Track success and failure outcomes for e-wallet disbursement via the shared disbursement_notif_url.

<Note>
  **Money Out Operation** — This webhook fires when an E-Wallet Top Up transaction is completed (success or failed), transferring funds from your SingaPay account to a customer's e-wallet.
</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 an E-Wallet Top Up transaction is completed (success or failed).

<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 `"ewallet-topup"` for this webhook. Use this to distinguish from `"disbursement"` 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: `"EW1512220251105174015668"`
    </ParamField>

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

    <ParamField body="notes" type="string">
      Transaction notes from the original request. Example: `"topup OVO pelanggan"`
    </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. `"00"` = Success, `"06"` = Failed
        </ParamField>

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

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

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

    <ParamField body="ewallet" type="object" required>
      E-wallet beneficiary information.

      <Expandable title="ewallet fields">
        <ParamField body="code" type="string" required>
          E-wallet provider code. E.g. `"OVO"`, `"DANA"`, `"GOPAY"`, `"LINKAJA"`, `"SHOPEEPAY"`
        </ParamField>

        <ParamField body="customer_number" type="string" required>
          Customer's e-wallet phone number or account number. Example: `"08123456789"`
        </ParamField>

        <ParamField body="customer_name" type="string">
          Customer's name on the e-wallet. `null` if account inquiry is not available.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="gross_amount" type="object" required>
      Total amount deducted from your merchant balance (net + fee).

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

        <ParamField body="value" type="string" required>
          Gross amount value. Example: `"50000.00"`
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="fee" type="object" required>
      Admin fee charged for this transaction.

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

    <ParamField body="net_amount" type="object" required>
      Net amount received by the customer (`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: `"47500.00"`</ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="balance_after" type="object" required>
      Your merchant account balance after the transaction.

      <Expandable title="balance_after fields">
        <ParamField body="currency" type="string">
          ISO 4217 currency code. Empty string if unavailable.
        </ParamField>

        <ParamField body="value" type="string">
          Balance value. Empty string if unavailable. Example: `"750000"`
        </ParamField>
      </Expandable>
    </ParamField>

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

    <ParamField body="failed_reason" type="string">
      Human-readable error description. Only present when the transaction failed. Example: `"Connection timeout to vendor"`
    </ParamField>
  </Expandable>
</ParamField>

***

### Payload Examples

<CodeGroup>
  ```json Success theme={null}
  {
      "response_code": "SP000",
      "response_message": "Successfully",
      "event": "ewallet-topup",
      "data": {
          "transaction_id": "EW101222025122910292195055674",
          "reference_number": "REF-EWALLET-001",
          "notes": "topup OVO pelanggan",
          "transaction_status": {
              "code": "00",
              "desc": "Success"
          },
          "post_timestamp": "1766978961000",
          "processed_timestamp": "1766978962000",
          "ewallet": {
              "code": "OVO",
              "customer_number": "08123456789",
              "customer_name": "Budi Santoso"
          },
          "gross_amount": {
              "currency": "IDR",
              "value": "50000.00"
          },
          "fee": {
              "currency": "IDR",
              "value": "2500.00"
          },
          "net_amount": {
              "currency": "IDR",
              "value": "47500.00"
          },
          "balance_after": {
              "currency": "IDR",
              "value": "750000"
          }
      }
  }
  ```

  ```json Failed theme={null}
  {
      "response_code": "SP001",
      "response_message": "Transaction Failure",
      "event": "ewallet-topup",
      "data": {
          "transaction_id": "EW121222025122617513896515436",
          "reference_number": "REF-EWALLET-002",
          "notes": "topup DANA pelanggan",
          "transaction_status": {
              "code": "06",
              "desc": "Failed"
          },
          "post_timestamp": "1766978900000",
          "processed_timestamp": "",
          "ewallet": {
              "code": "DANA",
              "customer_number": "08198765432",
              "customer_name": null
          },
          "gross_amount": {
              "currency": "IDR",
              "value": "100000.00"
          },
          "fee": {
              "currency": "IDR",
              "value": "2500.00"
          },
          "net_amount": {
              "currency": "IDR",
              "value": "97500.00"
          },
          "balance_after": {
              "currency": "IDR",
              "value": "850000"
          },
          "failed_code": "CONNECTION_ERROR",
          "failed_reason": "Connection timeout to vendor"
      }
  }
  ```
</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`).

***

## E-Wallet Top Up 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 top up.

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

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

processEwalletTopUp($payload);
```

### Timestamp Formats

<Warning>
  E-Wallet Top Up 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 | `1766978961000` | Divide by 1000 to convert          |
| `processed_timestamp` (body) | Unix milliseconds | `1766978962000` | Empty string if transaction failed |

When validating signatures, always use the **X-Timestamp header value** (in seconds), not the timestamps from the request body (which are in milliseconds).

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

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

### Balance Refund on Failure

When an E-Wallet Top Up fails, SingaPay **automatically refunds** the full `gross_amount` (net + fee) back to your merchant balance. You do not need to request a manual refund — the balance is restored immediately upon failure detection. The `balance_after` field reflects the post-refund balance.

### Balance Tracking

The `balance_after` field shows your account balance after the transaction:

* For **successful** transactions: remaining balance after deduction
* For **failed** transactions: balance after automatic refund (balance is restored)
* Use this to verify balance consistency with your system
* All amounts are in IDR (Indonesian Rupiah)

***

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