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

# Xendit

> Set up Xendit on your own account in the Philippines, Indonesia, Thailand, Malaysia or Vietnam: the secret API key, the session webhook and verification token.

## Collect payments with Xendit

`XENDIT` sends the payer to a **Xendit-hosted payment page**. There the payer
picks a local method: GCash or Maya in the Philippines, QRIS, e-wallets or bank
transfer in Indonesia, PromptPay in Thailand, FPX online banking in Malaysia,
e-wallets in Vietnam, or a card. Which methods appear is up to you: Xendit
shows every method you have activated on your account.

Centipid Billing creates a Xendit payment session, sends the payer to its
payment link, and confirms the result on a webhook that is re-checked against
Xendit before anything is credited.

The money goes into **your own Xendit account**. Centipid Billing never holds
it. You open the account with Xendit yourself and paste its keys here.

Offered in the country catalog for the five countries where Xendit opens
accounts: the Philippines, Indonesia, Thailand, Malaysia and Vietnam.

## Before you start

1. Open a Xendit account owned by the ISP and finish verification, so it can
   take live payments.
2. Activate the payment methods you want payers to see, under **Payment
   methods** in the Xendit dashboard.
3. Check your billing currency. Xendit bills each country in one currency:
   `PHP` in the Philippines, `IDR` in Indonesia, `THB` in Thailand, `MYR` in
   Malaysia and `VND` in Vietnam. Your Centipid Billing account must be in that
   currency, or every payment is refused before it reaches Xendit.
4. Plan the walled garden: the payer's browser must reach Xendit's payment
   page, so a Hotspot subscriber who is not yet online cannot pay unless Xendit
   is reachable before login. Centipid Billing adds Xendit's hosts to your
   routers' walled garden when Xendit is your connected gateway.

<Warning>
  An e-wallet or online-banking payment can hand the payer over to the
  provider's own website (GCash, Maya, a Malaysian bank's FPX page). That
  website is not Xendit's, so it cannot be added to the walled garden in
  advance. A Hotspot payer who is not yet online may be stopped there. QR
  methods (QRIS, PromptPay, QR Ph) are paid in the payer's banking app over
  mobile data and are not affected. Subscribers paying from PPPoE or from the
  customer portal are already online and are not affected either.
</Warning>

## Configure the gateway

<Steps>
  <Step title="Open Settings → Payments → Xendit">
    Select Xendit from the payment marketplace.
  </Step>

  <Step title="Enter the secret API key">
    In Xendit, open **Settings → Developers → API keys** and create a secret
    key with write access to Money-in. It starts with `xnd_production_` on a
    live account and `xnd_development_` in test mode. The public key is not
    needed and is refused.
  </Step>

  <Step title="Add the webhook in Xendit">
    Copy the **Webhook URL** shown under Callback URLs on the Xendit settings
    page. In Xendit, open **Settings → Developers → Webhooks** and paste it
    into **Payment Session – Completed**. Leave the legacy **Payment Link**
    (invoice) webhook alone: Centipid Billing does not use it.
  </Step>

  <Step title="Enter the webhook verification token">
    On the same Webhooks page, reveal the **verification token** and paste it
    into **Webhook verification token**.
  </Step>

  <Step title="Run a controlled test payment">
    Pay a small amount from the captive portal on a device that is not logged
    in, and confirm the payment reaches the ledger and the subscriber connects.
  </Step>
</Steps>

## Credentials

| Field | Required | Notes |
| - | - | - |
| Secret API key | Yes | Secret. `xnd_production_…` or `xnd_development_…`. |
| Webhook verification token | Yes | Secret. From **Settings → Developers → Webhooks** in Xendit. |

Both fields gate the switch. Test mode and live mode have separate keys and
separate verification tokens.

## Callback URLs

| Name | Path |
| - | - |
| Webhook URL | `https://<your-host>/api/xendit/callback` |

The payer's browser returns to `https://<your-host>/api/xendit/return` after
paying. Centipid Billing sends that address with every payment, always over
HTTPS because Xendit refuses anything else, so there is nothing to register
for it.

<Warning>
  Xendit sends the verification token in the `x-callback-token` header of every
  webhook. A wrong or missing token means every webhook is rejected with HTTP
  400\. Payers who wait for the return page are still connected, but a payer who
  closes the tab before it loads is charged and left disconnected until someone
  reconciles by hand.
</Warning>

## What the payer must provide

Whatever the chosen method asks for: an e-wallet approval, a QR scan in a
banking app, an online-banking login or card details.

<Note>
  Amounts are sent in the main currency unit, not cents: ₱50 is sent as `50`.
  Rupiah and dong are sent as whole numbers; pesos, baht and ringgit keep two
  decimals.
</Note>

## How a payment completes

<Steps>
  <Step title="Create the payment session">
    Centipid Billing creates a Xendit payment session for the amount, tagged
    with our own reference, and writes a pending row against that reference.
  </Step>

  <Step title="Payment">
    The payer pays on Xendit's page and is sent back to the return page. A
    payer who chose a method paid later, such as an Indonesian virtual account
    or an Alfamart code, is told the payment is being confirmed and not to pay
    again.
  </Step>

  <Step title="Confirmation">
    Xendit posts the `payment_session.completed` webhook. The token is checked,
    then the session Centipid Billing created for this payment is read back
    from the Xendit API. Only a session that is `COMPLETED` records a payment
    and applies the package. The return page runs the same check. A repeated
    webhook updates the same payment rather than crediting it twice.
  </Step>
</Steps>

Webhooks for sessions that other software created on the same Xendit
account are acknowledged and ignored, and so are expired sessions.

<Warning>
  A Xendit account has one **Payment Session – Completed** webhook URL. Two
  ISPs can share one Xendit account without a payment ever being credited to
  the wrong one, but only the ISP whose URL is registered receives webhooks.
  The other connects payers only when they reach the return page, so a payer
  who closes the tab first stays disconnected until someone reconciles by
  hand. Give each ISP its own Xendit account.
</Warning>

If Xendit's API cannot be reached when a webhook arrives, Centipid Billing
answers with HTTP 503 so Xendit sends the webhook again later.

## Troubleshooting

| Symptom | What it means | What to do |
| - | - | - |
| *"Xendit bills in PHP here, but your prices are in USD."* | Your billing currency is not the one Xendit uses in your country. | Contact Centipid support to change your billing currency. |
| *"Unable to process payment. Please try again later."* | The secret key is missing, lacks Money-in write access, or Xendit refused the payment session. | Check the key and its permissions, and that the account is verified for live payments. |
| A Hotspot payer is stopped on GCash, Maya or a bank page | The provider's own website is not reachable before login. | Ask the payer to pay by QR from mobile data, or to buy a voucher instead. |
| Test payments work, live does not | The key is still `xnd_development_…`. | Replace it with the live key, and copy the live-mode verification token too. |
| The payment page never loads on a Hotspot device | The walled garden does not allow Xendit. | Confirm Xendit is the connected gateway so the routers are synced, then see [Captive device cannot reach checkout](/billing/payment-gateways/troubleshooting). |
| Payments succeed in Xendit, ledger stays empty | Webhooks are being rejected or not sent. | In Xendit, open the webhook log. A 400 means the verification token is wrong: re-copy it. No attempts means the URL is not under **Payment Session – Completed**. |

For every gateway's fields in one place, see the
[credential reference](/billing/payment-gateways/credentials-reference).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.