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

# Custom (REST API)

> Take phone payments from any voice platform: two API calls and one call transfer.

If your platform can make HTTPS requests and transfer a phone call, it can take payments with
Ringup. No SDK to install, no plugin to configure: your agent calls the REST API directly, then
hands the caller to Ringup's hosted payment line, which handles the card conversation, consent,
the charge, and the spoken confirmation.

The whole integration is two API calls per phone call, plus one transfer.

## Before you start

<Steps>
  <Step title="Get your API key">
    Sign in to the [dashboard](https://ringup.dev/dashboard/) and copy your test key. It starts
    with `rk_test_` and works against real endpoints with no real money: every charge settles
    against a test processor account.
  </Step>

  <Step title="Create a test merchant">
    In the dashboard, create a merchant. In test mode it is automatically backed by Ringup's
    test processor account, so there is no OAuth step and no real merchant account needed. The
    `merchant_id` you get (like `mch_1fd0e6c96e6dcd7681f2`) identifies whose processor gets paid.
  </Step>
</Steps>

## 1. Recognize the caller

Call `identify` when the call starts, so your agent can greet a returning customer by name. One
request, identity only: whether they can pay with a saved card depends on the merchant, and that
answer comes later, from the checkout.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.ringup.dev/v1/identify \
    -H "Authorization: Bearer rk_test_5f2a8c1d9e4b7a3f6c0d" \
    -H "Content-Type: application/json" \
    -d '{"caller_phone": "+14155550142"}'
  ```

  ```javascript Node theme={null}
  const res = await fetch("https://api.ringup.dev/v1/identify", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.RINGUP_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ caller_phone: "+14155550142" }),
  });
  const caller = await res.json();
  // caller.known === true -> greet them: caller.consumer.first_name
  ```

  ```python Python theme={null}
  import os, requests

  res = requests.post(
      "https://api.ringup.dev/v1/identify",
      headers={"Authorization": f"Bearer {os.environ['RINGUP_API_KEY']}"},
      json={"caller_phone": "+14155550142"},
  )
  caller = res.json()
  # caller["known"] is True -> greet them: caller["consumer"]["first_name"]
  ```
</CodeGroup>

```json Response theme={null}
{
  "known": true,
  "consumer": {
    "id": "con_9c41f2ab8d6e37105b24",
    "first_name": "Alex",
    "last_name": "Rivera",
    "email": "alex@example.com"
  }
}
```

## 2. Create the checkout

When the order is settled, create the Checkout Session. One response carries everything your
agent needs to finish the call: the resolved total, whether payment is required, the exact line
to speak, and both transfer destinations.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.ringup.dev/v1/create_checkout \
    -H "Authorization: Bearer rk_test_5f2a8c1d9e4b7a3f6c0d" \
    -H "Content-Type: application/json" \
    -d '{
      "merchant_id": "mch_1fd0e6c96e6dcd7681f2",
      "caller_phone": "+14155550142",
      "order_id": "ORDER-8841"
    }'
  ```

  ```javascript Node theme={null}
  const res = await fetch("https://api.ringup.dev/v1/create_checkout", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.RINGUP_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      merchant_id: "mch_1fd0e6c96e6dcd7681f2",
      caller_phone: "+14155550142",
      order_id: "ORDER-8841",
    }),
  });
  const checkout = await res.json();
  ```

  ```python Python theme={null}
  import os, requests

  res = requests.post(
      "https://api.ringup.dev/v1/create_checkout",
      headers={"Authorization": f"Bearer {os.environ['RINGUP_API_KEY']}"},
      json={
          "merchant_id": "mch_1fd0e6c96e6dcd7681f2",
          "caller_phone": "+14155550142",
          "order_id": "ORDER-8841",
      },
  )
  checkout = res.json()
  ```
</CodeGroup>

```json Response theme={null}
{
  "checkout_session_id": "cs_test_453b5401286d0f80",
  "merchant_id": "mch_1fd0e6c96e6dcd7681f2",
  "agent_message": "Payment is ready. Transferring the caller now.",
  "payment_required": "required",
  "amount_cents": 4599,
  "currency": "USD",
  "amount_source": "order",
  "transfer_to": "sip:eyJrIjoiY3NfdGVzdF80NTNiNTQwMTI4NmQwZjgwIiwieCI6MTc4NDk1MjU3MTU5N30.vSkGB9zGRuR6fRhRHxmcTvYQZ5mi@transfer.ringup.dev",
  "transfer_to_number": "+13235047170",
  "expires_at": "2026-07-25T04:09:12.000Z",
  "cards": [
    {
      "instrument_id": "ins_7b3f6c0d9e4b5f2a8c1d",
      "brand": "VISA",
      "last_four": "1111"
    }
  ],
  "default_card_id": "ins_7b3f6c0d9e4b5f2a8c1d",
  "metadata": null
}
```

<Note>
  `amount_cents` came from the merchant's own order (`amount_source: "order"`), because the request
  named an `order_id`. Send `amount_cents` yourself only when there is no order to resolve it from.
</Note>

## 3. Transfer the call

This is the only decision in the integration, made once at setup, based on what your platform
can dial:

<ParamField path="transfer_to" type="SIP URI">
  The default. A SIP address carrying a signed, single-session credential (about 156 characters).
  Deterministic: the payment line reads the session directly from it. Use this whenever your
  platform can transfer a call to a SIP URI.
</ParamField>

<ParamField path="transfer_to_number" type="E.164 phone number">
  The compatibility path, for platforms that can only transfer calls to a phone number. The call
  arrives without the credential and the payment line resolves the session by the caller's number,
  which Ringup stored when you called `create_checkout`. The caller's number must survive your
  platform's transfer; nearly all number transfers preserve it.
</ParamField>

Both fields are present on every response where payment is required, so your code survives a
platform switch. Speak `agent_message`, transfer to your chosen destination, and your agent's job
is done: the hosted line takes the payment, announces the confirmation, and ends or returns the
call.

<Warning>
  If `payment_required` is `"none"`, there is nothing to collect: skip the transfer and keep the
  caller. `agent_message` already says the right thing for every outcome, so your agent can always
  speak it verbatim.
</Warning>

## 4. Hear the result

The moment the payment lands (or fails), Ringup tells you two ways. Use whichever fits; both
carry the same facts.

**Webhook** (push): register your endpoint once in the dashboard under **Webhooks** (the
signing secret is shown once), or script it with
[`POST /v1/webhook_endpoints`](/concepts/webhooks), and receive
[`checkout.succeeded`](/api-reference/events#checkout-succeeded) or
[`checkout.failed`](/api-reference/events#checkout-failed), with your `order_id` and
`platform_call_id` echoed so you can mark the order paid in your own system without a lookup.

**Session read** (pull):

<CodeGroup>
  ```bash curl theme={null}
  curl https://api.ringup.dev/v1/checkout_sessions/cs_test_453b5401286d0f80 \
    -H "Authorization: Bearer rk_test_5f2a8c1d9e4b7a3f6c0d"
  ```

  ```javascript Node theme={null}
  const res = await fetch(
    "https://api.ringup.dev/v1/checkout_sessions/cs_test_453b5401286d0f80",
    { headers: { Authorization: `Bearer ${process.env.RINGUP_API_KEY}` } },
  );
  const session = await res.json();
  // session.session_status === "complete" and session.charge.status === "succeeded"
  ```

  ```python Python theme={null}
  import os, requests

  res = requests.get(
      "https://api.ringup.dev/v1/checkout_sessions/cs_test_453b5401286d0f80",
      headers={"Authorization": f"Bearer {os.environ['RINGUP_API_KEY']}"},
  )
  session = res.json()
  # session["session_status"] == "complete" and session["charge"]["status"] == "succeeded"
  ```
</CodeGroup>

## Go live

Swap `rk_test_` for your `rk_live_` key and connect the merchant's real payment processor in the
dashboard. The endpoints, fields, and your code stay exactly the same: the key selects the
environment, and every id you receive says which one it belongs to (`cs_test_…` / `cs_live_…`).

## Next steps

<CardGroup cols={2}>
  <Card title="API reference" icon="code" href="/api-reference/introduction">
    Every endpoint, parameter, and error this guide touched.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/concepts/webhooks">
    Delivery guarantees, signatures, and retries for the result events.
  </Card>
</CardGroup>
