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

# PHP Offramp

> Sell stablecoin for Philippine Pesos, paid out directly to a Philippine bank account via InstaPay or PESONet.

PHP offramp lets your users convert their stablecoin balance into Philippine Pesos, settled to a bank account in the Philippines. Every offramp flow follows the same four steps: **Create User → KYC → Bank Account Linkage → Transaction.**

<Info>
  Both USDT and USDC are supported for PHP offramp.
</Info>

<Steps>
  <Step title="Create User">
    ```http theme={null}
    POST /api/v2/user
    ```

    ```json theme={null}
    {
      "email": "user@example.com",
      "phone": "+639171234567"
    }
    ```

    Response:

    ```json theme={null}
    {
      "success": true,
      "data": {
        "user_id": "550e8400-e29b-41d4-a716-446655440000"
      }
    }
    ```

    Save `user_id` — every subsequent call needs it as the `X-User-Id` header.
  </Step>

  <Step title="KYC">
    PHP supports two KYC methods:

    <Tabs>
      <Tab title="KYC Sharing">
        ```http theme={null}
        POST /api/v2/user/kyc
        ```

        Headers: `X-User-Id: <user_id>`

        ```json theme={null}
        {
          "legal_name": "Juan dela Cruz",
          "poi": "<proof-of-identity-document-id>",
          "poa": "<proof-of-address-document-id>",
          "dob": "1990-01-31",
          "email": "user@example.com",
          "phone": "+639171234567",
          "country_iso3": "PHL"
        }
        ```

        Response:

        ```json theme={null}
        { "success": true, "data": {} }
        ```

        Saber may request supporting KYC documents at any time — these must be shared within 72 hours of the request.
      </Tab>

      <Tab title="KYC Widget">
        Redirect the user to a signed URL:

        ```javascript theme={null}
        const sigString = clientId + timestamp + "sdk" + userId
        const secret = HmacSHA256(sigString, clientSecret).toString().toUpperCase()
        // redirect to: https://app.saber.money/kyc?client_id=...&user_id=...&timestamp=...&secret=...&redirect_url=<url-encoded-url>
        ```

        The URL is valid for **10 minutes** from generation. Append the optional `redirect_url` as the last query parameter (URL-encoded) to send the user back to your app when they exit the flow. See [KYC Hosted Widget](/user/kyc/widget) for the full parameter reference.
      </Tab>
    </Tabs>
  </Step>

  <Step title="Bank Account Linkage">
    First, fetch the list of supported banks and their codes. This lookup only works for `PHL` — it returns an error for any other country:

    ```http theme={null}
    GET /api/v2/user/bank_account/bank-codes/PHL
    ```

    ```json theme={null}
    {
      "success": true,
      "data": {
        "BDO Unibank": "BDO",
        "Bank of the Philippine Islands": "BPI",
        "Metropolitan Bank and Trust Company": "MBTC",
        "Land Bank of the Philippines": "LBP"
      }
    }
    ```

    `data` is a flat map of bank name to `bank_code` — pass the code as-is into `bank_code` below.

    ```http theme={null}
    POST /api/v2/user/bank_account
    ```

    Headers: `X-User-Id: <user_id>`

    ```json theme={null}
    {
      "account_number": "123456789012",
      "account_holder_name": "Juan dela Cruz",
      "bank_code": "BDO",
      "bank_name": "BDO Unibank",
      "bank_bic": "BOPIPHMM",
      "country": "PHL"
    }
    ```

    For the Philippines, `account_number` (no spaces) and `account_holder_name` are required, plus at least one of `bank_code` or `swift_code`; `bank_name` is accepted alongside them.

    Response:

    ```json theme={null}
    {
      "success": true,
      "data": {
        "bank_id": "550e8400-e29b-41d4-a716-446655440000",
        "account_number": "1234XXXX9012",
        "ifsc_code": "",
        "account_holder_name": "Juan dela Cruz",
        "account_type": null,
        "name": "BDO Unibank",
        "iban": null,
        "bic": null,
        "validation_status": "PENDING",
        "routing_type": null,
        "created_at": "2024-01-01T00:00:00Z",
        "updated_at": "2024-01-01T00:00:00Z",
        "message": ""
      }
    }
    ```

    `created_at`/`updated_at` here are ISO 8601 date strings — unlike wallet-transaction endpoints, which use epoch milliseconds. Save `bank_id` for the sell step.

    <Info>
      Unlike India, PHP bank accounts are approved **instantly** — there's no penny-drop step.
    </Info>

    <Note>
      A user can have up to **4 `APPROVED` bank accounts** at any point in time (error `304012` when exceeded).
    </Note>
  </Step>

  <Step title="Transaction">
    Min ₱60 (\~\$1) per transaction; max depends on the user's assigned limits (see [User Limits & EDD](#user-limits-and-edd) below). `payment_method: bank_transfer` resolves internally to one of:

    * **InstaPay** — for amounts up to ₱50,000. Instant, no cut-offs. Banking fee: ₱8.
    * **SwiftPay PESONet** — for amounts above ₱50,000. T+1 settlement on working days. Banking fee: ₱4.

    Two variants, both requiring a quote first — a **Live Quote** (informational, executes at market rate regardless) or a **Locked Quote** (rate held \~30-60s, supplied as `quote_id` in the sell request):

    <Tabs>
      <Tab title="Pool Sell (recommended)">
        Get an informational quote:

        ```http theme={null}
        GET /api/v2/wallet/s/quote?from_currency=USDT&to_currency=PHP&from_amount=100
        ```

        ```json theme={null}
        {
          "success": true,
          "data": {
            "from_currency": "USDT",
            "to_currency": "PHP",
            "from_amount": 100,
            "pre_fee_to_amount": 5650,
            "to_amount": 5600,
            "base_price": 56.5,
            "final_price": 56,
            "total_fee": 50,
            "fee_currency": "PHP",
            "fee_breakup": {
              "platform_fee": 20,
              "network_fee": 0,
              "client_fee": 10,
              "discount": 0,
              "tax_on_fee": 20,
              "tds": 0
            }
          }
        }
        ```

        Then create the sell:

        ```http theme={null}
        POST /api/v2/wallet/transaction/pool/sell
        ```

        ```json theme={null}
        {
          "source_id": "<bank_id>",
          "fiat_symbol": "PHP",
          "crypto_symbol": "USDT",
          "crypto_amount": 25,
          "payment_method": "bank_transfer"
        }
        ```

        Response:

        ```json theme={null}
        {
          "success": true,
          "data": {
            "id": "07825d5d-1879-4cc7-a87f-f5ed45ee391b",
            "transaction_type": "POOL_SELL",
            "status": "PROCESSING",
            "fiat_amount": 1410.5,
            "crypto_amount": 25,
            "fiat_symbol": "PHP",
            "crypto_symbol": "USDT",
            "exchange_rate": 56.42,
            "failure_code": "",
            "failure_desc": "",
            "created_at": 1774343695000,
            "user_id": "65d246fc-6a5b-43d7-89bf-944e95ff2279"
          }
        }
        ```
      </Tab>

      <Tab title="External Sell">
        Generate a locked quote (expires in \~30-60s):

        ```http theme={null}
        GET /api/v2/wallet/w/quote?from_currency=USDT&to_currency=PHP&network=BSC&from_amount=10
        ```

        ```json theme={null}
        {
          "success": true,
          "data": {
            "from_currency": "USDT",
            "to_currency": "PHP",
            "network": "BSC",
            "from_amount": 10,
            "pre_fee_to_amount": 563.2,
            "to_amount": 563.2,
            "base_price": 56.32,
            "final_price": 56.32,
            "total_fee": 0,
            "fee_currency": "PHP",
            "fee_breakup": {
              "platform_fee": 0,
              "network_fee": 0,
              "client_fee": 0,
              "discount": 0,
              "tax_on_fee": 0,
              "tds": 0
            },
            "other_details": {
              "fiat_settlement_time": { "min": 0, "max": 1, "unit": "HOURS" }
            },
            "transaction_type": "EXTERNAL_SELL",
            "id": "0c6ad6d3-6902-4a27-a30f-c176456a0835",
            "expiry": "2026-03-25T09:42:14.321113044Z",
            "settlement_expiry": "2026-03-25T10:41:44.321113044Z",
            "created_at": "2026-03-25T09:41:44.318189513Z",
            "updated_at": "2026-03-25T09:41:44.318189595Z"
          }
        }
        ```

        Save `id` from the response as `quote_id`, then create the sell:

        ```http theme={null}
        POST /api/v2/wallet/transaction/crypto/sell
        ```

        ```json theme={null}
        {
          "payment_method": "bank_transfer",
          "network": "BSC",
          "destination_instrument_id": "<bank_id>",
          "quote_id": "<quote_id>",
          "sender_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8",
          "refund_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8"
        }
        ```

        Response:

        ```json theme={null}
        {
          "success": true,
          "data": {
            "id": "01992e48-db7d-7c55-bb87-6f4e1abf5fe3",
            "status": "CREATED",
            "fiat_symbol": "PHP",
            "crypto_symbol": "USDT",
            "fiat_amount": 563.2,
            "crypto_amount": 10,
            "payment_method": "bank_transfer",
            "deposit_crypto_address": "0xabcdef1234567890abcdef1234567890abcdef12",
            "deposit_network": "BSC",
            "destination_instrument_id": "a4680ba9-5ac6-4629-9493-2a88b160cd33",
            "refund_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8",
            "refund_network": "",
            "refund_tag": "",
            "sender_wallet_address": "0x318d2aae4c99c2e74f7b5949fa1c34df837789b8",
            "created_at": 1757418150839,
            "updated_at": 1757418150841
          }
        }
        ```

        Then fund `deposit_crypto_address` on-chain within the settlement expiry, and confirm it:

        ```http theme={null}
        POST /api/v2/wallet/transaction/crypto/sell/{id}/confirm-onchain-funding
        ```

        ```json theme={null}
        { "transaction_hash": "0x1234567890abcdef" }
        ```

        <Warning>
          This returns **202 Accepted**, not 200 — Saber verifies the deposit asynchronously.
        </Warning>

        ```json theme={null}
        {
          "success": true,
          "data": {
            "id": "01992e48-db7d-7c55-bb87-6f4e1abf5fe3",
            "status": "CREATED",
            "transaction_hash": "0x1234567890abcdef",
            "fiat_symbol": "PHP",
            "crypto_symbol": "USDT"
          }
        }
        ```
      </Tab>
    </Tabs>
  </Step>
</Steps>

## Checking status

```http theme={null}
GET /api/v2/wallet/transaction?transaction_type={POOL_SELL|EXTERNAL_SELL}&transaction_id={id}
```

| Transaction Type | Possible `status` values                                                                                                                                           |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Pool Sell        | `CREATED`, `PROCESSING`, `COMPLETED`, `FAILED`, `CANCELLED`, `APPROVAL_REQUIRED`, `SOURCE_INVALID`, `VERIFICATION_PENDING`, `REFUND_INITIATED`, `REFUND_COMPLETED` |
| External Sell    | `CREATED`, `PAYMENT_INITIATED`, `FUNDS_RECEIVED`, `COMPLETED`, `FAILED`, `REFUND_INITIATED`, `REFUND_COMPLETED`, `REFUND_FAILED`                                   |

<Accordion title="Example status responses">
  Pool Sell:

  ```json theme={null}
  {
    "success": true,
    "data": {
      "transaction_type": "POOL_SELL",
      "status": "COMPLETED",
      "created_at": 1742207345000,
      "updated_at": 1742207346000,
      "id": "0195a3a6-62c2-766b-b984-7f9f1754b3f1",
      "crypto_symbol": "USDT",
      "fiat_symbol": "PHP",
      "exchange_rate": 56.4,
      "crypto_amount": 21,
      "fiat_amount": 1184.4,
      "bank_transaction_id": "1234",
      "source_id": ""
    }
  }
  ```

  External Sell:

  ```json theme={null}
  {
    "success": true,
    "data": {
      "id": "01992e48-db7d-7c55-bb87-6f4e1abf5fe3",
      "transaction_type": "EXTERNAL_SELL",
      "status": "FUNDS_RECEIVED",
      "created_at": 1757418150839,
      "updated_at": 1757418150841,
      "crypto_symbol": "USDT",
      "fiat_symbol": "PHP",
      "crypto_amount": 0.695712,
      "fiat_amount": 55.38,
      "deposit_crypto_address": "0xabcdef1234567890abcdef1234567890abcdef12",
      "deposit_network": "BSC",
      "destination_instrument_id": "a4680ba9-5ac6-4629-9493-2a88b160cd33",
      "bank_transaction_id": "",
      "source_id": ""
    }
  }
  ```
</Accordion>

Poll until the status reaches a terminal state, or configure a webhook URL to receive completion pushes instead — see [Order Lifecycle and Webhooks](/offramp/operations/lifecycle).

## User limits and EDD

Each user has a lifetime transaction limit (currently \$200,000 USD equivalent — this figure is suggestive and subject to change; confirm current limits with Saber). Once a user's limit is breached, no further transactions can be created until Enhanced Due Diligence (EDD) is completed to raise it.

EDD is completed via the [EDD Hosted Widget](/users/limits-edd/widget), with a turnaround time of 2-3 working days.

## Related pages

<CardGroup cols={2}>
  <Card title="Offramp Operations" icon="arrow-right-arrow-left" href="/offramp/available-flows" cta="View guide" arrow="true" />

  <Card title="User KYC" icon="id-card" href="/user/kyc/overview" cta="View guide" arrow="true" />

  <Card title="Bank Account" icon="building-columns" href="/user/bank-account/overview" cta="View guide" arrow="true" />
</CardGroup>


## Related topics

- [Overview](/user/kyc/overview.md)
- [Available Flows](/offramp/available-flows.md)
- [Welcome to Saber Money](/getting-started/welcome.md)
