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

# Payins quickstart

> Take your first payment in under 10 minutes. Collected in crypto, credited to the same balance your payouts spend from.

A **payin** is money coming *in* — a payment from your customer to you. It is the mirror of a
[payout](/api-reference/send-payout), which is money going out from you to a recipient.

Today payins are collected through the **crypto payment gateway**: your customer pays in a
stablecoin on a hosted checkout page, and we credit your PontisGlobe balance. That balance is the
same one your payouts spend from, so money you take in can be paid out again with no transfer step.

<Info>
  Crypto is currently the only payin rail. The integration itself is not crypto-specific — you
  create a payment, redirect the customer, and act on a callback. If other rails are added, those
  three steps do not change; only the fields describing the crypto leg stop being the only option.
</Info>

## Before you start

Four things must be set up on your account. Ask your account manager — none of them are
self-service:

| Requirement                | Why                                                                          |
| -------------------------- | ---------------------------------------------------------------------------- |
| **Payins enabled**         | Without it every call returns `product_not_enabled`                          |
| **A merchant profile**     | Your display name, logo and brand colour on the checkout page                |
| **Allowed redirect hosts** | `success_url` / `fail_url` hosts are allow-listed; anything else is rejected |
| **Accepted assets**        | Which currencies your customer may pay in. Defaults to USDT only             |

You will also need your API credentials from
[Developer Tools](https://app.pontisglobe.com/developer-tools) — the same **API Key**, **Encryption
Secret** and **HMAC Secret** the payout API uses.

***

## 1. Set up the request helper

Payins use the identical transport as every other endpoint: an AES-256-GCM encrypted body, an
HMAC-signed request, and your API key. If you already integrated payouts, **reuse the helper you
already have** — there is nothing new to write.

If you are starting fresh, copy the `call()` helper from
[Quickstart step 2](/quickstart#2-set-up-the-request-helper). It is available in Node, Python, Go
and Java.

<Note>
  Payin endpoints take **no JWT**. Creating a payment link moves no money by itself, so it is
  authenticated by your API key, request signature and IP allowlist. A payin-only integration never
  calls `/api/v1/user/login`.
</Note>

## 2. Create the payment

Call [`POST /api/v1/gateway/payments/create`](/api-reference/create-payin) with your own order
reference and the amount.

<CodeGroup>
  ```js Node theme={null}
  const created = await call('/api/v1/gateway/payments/create', {
    merchant_reference: 'ORDER-1042', // your order id — also the idempotency key
    amount: '100.00',
    currency: 'USDT',
    success_url: 'https://shop.example.com/thanks',
    fail_url: 'https://shop.example.com/cancelled',
  })

  const { checkout_url, expires_at } = created.data
  ```

  ```python Python theme={null}
  status, body = call('/api/v1/gateway/payments/create', {
      'merchant_reference': 'ORDER-1042',
      'amount': '100.00',
      'currency': 'USDT',
      'success_url': 'https://shop.example.com/thanks',
      'fail_url': 'https://shop.example.com/cancelled',
  })

  checkout_url = body['data']['checkout_url']
  ```

  ```go Go theme={null}
  payload := map[string]interface{}{
    "merchant_reference": "ORDER-1042",
    "amount":             "100.00",
    "currency":           "USDT",
    "success_url":        "https://shop.example.com/thanks",
    "fail_url":           "https://shop.example.com/cancelled",
  }
  res, _ := Call("/api/v1/gateway/payments/create", payload)
  ```

  ```java Java theme={null}
  String jsonBody = """
    {"merchant_reference":"ORDER-1042","amount":"100.00","currency":"USDT",
     "success_url":"https://shop.example.com/thanks",
     "fail_url":"https://shop.example.com/cancelled"}
  """;
  HttpResponse<String> res = PontisClient.call("/api/v1/gateway/payments/create", jsonBody);
  ```
</CodeGroup>

You get back a `checkout_url` and an `expires_at`.

<Info>
  **`merchant_reference` is your idempotency key.** Reusing it returns the existing payment instead
  of creating a second one, so a retry after a network timeout is safe and hands you back the same
  `checkout_url`. Use one reference per order.
</Info>

## 3. Redirect your customer

Send them to `checkout_url`. That page is hosted by us and carries your branding.

<Warning>
  Treat `checkout_url` as a **bearer credential**. Anyone holding it can see the order amount and
  the deposit address. Send it to the customer directly — do not log it, email it to a shared inbox,
  or put it anywhere indexable.
</Warning>

On that page your customer picks a currency and network, sees a deposit address and a QR code, and
sends the funds from their own wallet. When they finish they are returned to your `success_url`;
if they cancel, to your `fail_url`.

<Warning>
  **Never fulfil the order on the browser redirect.** A customer can close the tab, lose signal, or
  pay and never come back. The redirect is a convenience for them; the callback is the fact.
</Warning>

## 4. Handle the callback

We POST a signed JSON body to your callback URL when the payment settles. **This is the event you
fulfil on** — full details in [Payin callbacks](/payin-callbacks).

```json theme={null}
{
  "event": "gateway_payment.paid",
  "gateway_payment_id": "0f8b…",
  "merchant_reference": "ORDER-1042",
  "status": "paid",
  "requested_amount": "100.00",
  "requested_currency": "USDT",
  "received_amount": "99.00",
  "merchant_fee_amount": "0.50",
  "credited_amount": "99.50",
  "credited_currency": "USDC",
  "paid_at": "2026-08-14T09:12:44.000Z"
}
```

Three things to get right, each covered on that page:

* **Verify the signature.** Same headers and same scheme as payout callbacks, so your existing
  verification code works unchanged.
* **Be idempotent.** Unlike payouts, payin callbacks are **retried** — up to 8 attempts — so the
  same event can arrive more than once.
* **Handle all three settled statuses separately.** `paid` and `overpaid` mean the customer paid in
  full — fulfil. `underpaid` means they paid **less**, by any amount, and must never auto-fulfil.

## 5. Confirm with a status lookup (optional)

[`POST /api/v1/gateway/payments/status`](/api-reference/get-payin-status) reads back a payment by
your own reference.

<Info>
  For **reconciliation and support**, not for driving fulfilment. Polling it in a loop will be rate
  limited and will always be slower than the callback already on its way to you.
</Info>

***

## What your customer can pay with

The checkout offers the assets **you** are configured to accept, on the networks we support for
each. It is a matrix, not a list — an asset is not available on every network:

| Asset | Networks           |
| ----- | ------------------ |
| USDT  | Tron, Ethereum     |
| USDC  | Ethereum, Optimism |

Two things follow from accepting more than one:

* **You end up holding whichever your customer chose.** Accepting USDC means you will have a USDC
  balance, separate from your USDT balance. There is no automatic conversion between them.
* **Your payout currencies must keep up.** A balance in an asset you are not enabled to pay out from
  is money you can see and cannot move. Ask for both to be enabled together.

## The three currencies on a payment

A single payin involves three currencies and they are frequently different. Confusing them is the
most common integration mistake here, so every response names each one explicitly:

| Field                | What it is                                                                                                                               |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `requested_currency` | What **you** priced the order in. Your own invoice currency                                                                              |
| `credited_currency`  | What your **customer** sent, and what `credited_amount` and `merchant_fee_amount` are denominated in. This is what lands in your balance |
| `received_currency`  | The currency of our internal settlement figure. Exposed for traceability only — never reconcile on it                                    |

<Warning>
  Reconcile against `credited_amount` **and** `credited_currency` together. `credited_amount` alone
  is ambiguous the moment you accept more than one asset.
</Warning>

On the status endpoint the same value is called `deposit_asset` — it is the asset the customer
deposited, which is by definition the one you were credited in.

## Amounts and fees

* `requested_amount` — what you asked for
* `merchant_fee_amount` — the total we charge you, as a single figure
* `credited_amount` — what reached your balance, after our fee

All amounts are **decimal strings**, never numbers — `"100.00"`, not `100.00`. Parsing them into a
floating-point type will eventually cost you precision on a real payment.

Order amounts accept at most **2 decimal places**. A finer amount is rejected at create time rather
than rounded, so your customer is never charged a different figure than your order says.

## Payment statuses

| Status             | Meaning                                                                         |
| ------------------ | ------------------------------------------------------------------------------- |
| `created`          | Link issued, customer has not chosen how to pay yet                             |
| `awaiting_payment` | Customer picked a method; we are waiting for funds on-chain                     |
| `paid`             | Settled for the expected amount. **Fulfil the order**                           |
| `underpaid`        | **Customer paid less than the order.** Credited anyway — **do not auto-fulfil** |
| `overpaid`         | Settled for more than expected. Credited in full — you owe the difference       |
| `expired`          | The window passed with no payment                                               |
| `failed`           | The payment could not be completed                                              |

### Expiry is not final

A checkout link has a deadline — **30 minutes by default**, set per payment with
`expires_in_seconds` (minimum 5 minutes, maximum 24 hours). When it passes, the payment stops being
one we expect to settle.

<Info>
  30 minutes is short on purpose: it is roughly how long a customer stays in a checkout, and the
  window is also how long we hold a deposit address open. If your customers pay from an exchange
  withdrawal rather than a self-custody wallet, raise it — those can take far longer to clear.
</Info>

**The deposit address stays live.** A customer who pays late is still credited, the status still
moves to `paid`, and you still get the callback. Expiry stops us waiting; it does not close the
door. Do not treat `expired` as a terminal refusal in your own system.

## Reconciliation

Payins appear in your monthly statement in their own **Payments Received** section, separate from
payouts, with totals per credited currency. A month in which you only took payments and sent none
still produces a statement.

They also appear in your [transactions list](https://app.pontisglobe.com/transactions) as
`gateway_deposit` rows, each showing the currency it was credited in.

## Testing in sandbox

Change the base URL to `https://sandbox.pontisglobe.com` and everything else stays the same. Nothing
is stored, no deposit address is reserved, and no money can move.

The outcome is driven by the last two digits of your `merchant_reference`, so you can reproduce a
paid, underpaid or overpaid payment on demand. Full walkthrough and the trigger table:
[Sandbox — payins](/sandbox-payins).

## Going live

Everything in [Going live](/going-live) applies. Additionally, for payins:

* **Redirect hosts registered for production** — your live `success_url` / `fail_url` hosts are
  allow-listed separately from any test ones
* **[Callback handler](/payin-callbacks) idempotent and signature-verified** — payin callbacks
  retry, so a handler that double-fulfils will double-fulfil in production
* **All three settled statuses handled, and `underpaid` handled *separately*** — confirm a partial
  payment does not reach your fulfilment path
* **Payout currencies cover the assets you accept** — otherwise a real customer payment lands in a
  balance you cannot move
* **One small live payment end to end** — confirm the callback arrives, the balance moves, and the
  transaction appears before you send customers to it
