Add card-on-file payments to an ElevenLabs agent by call transfer. This works like a hosted web
checkout, over the phone: when the order is confirmed, your agent creates a Checkout Session
and transfers the live call to a Ringup payment line, the caller pays there, and the call
optionally returns to your agent. Ringup recognizes the caller, charges their saved card,
settles it against the order, and texts a receipt. Your server reconciles from a webhook. If the
model is new to you, read Call transfer first.
This page is end to end: first-time setup, the call, the return, and what happens after payment.
How the pieces map to ElevenLabs
| Ringup piece | On ElevenLabs |
|---|
create_checkout | A server tool on your agent that creates a Checkout Session and returns where to transfer |
| The transfer target | The transfer_to SIP URI the Checkout Session returns |
| The transfer | The transfer_to_number system tool with transferType: sip_refer and customSipHeaders |
| The result | A checkout.completed webhook to your server |
Prerequisites
- An ElevenLabs agent you already run, with a phone number attached over a SIP trunk (custom
SIP headers require a SIP trunk, not a native-Twilio number; see the warning below).
- Your ElevenLabs API key.
- A Ringup account and API key (from your Ringup dashboard). Test mode works out of the box on a
shared sandbox, so there is nothing to connect. See Testing.
This is the one-time setup. On your ElevenLabs agent, add a server tool that calls Ringup’s
create_checkout endpoint with your Ringup API key (Agent, then Tools, then Add tool, then
Webhook), and confirm the built-in transfer_to_number system tool is enabled.
{
"type": "webhook",
"name": "create_checkout",
"description": "Create a Ringup Checkout Session for the order and return where to transfer the call.",
"url": "https://api.ringup.dev/v1/checkouts",
"method": "POST",
"request_headers": { "Authorization": "Bearer <YOUR_RINGUP_KEY>" },
"body_parameters": {
"amount_cents": { "type": "number", "description": "Order total in cents" },
"order_id": { "type": "string", "description": "Your processor order id, if you have one" }
}
}
Then add one line to your agent’s prompt:
When the caller has confirmed their order, call create_checkout with the order total, then
transfer to the returned target.
Step 2: Create a Checkout Session when the order is confirmed
Your agent calls the tool from Step 1. It takes the same inputs a hosted checkout session takes
(amount, line items, order), plus how you want the call to end, and returns where to send the
call:
create_checkout({
"amount_cents": 2500,
"line_items": [{ "name": "Large pepperoni", "quantity": 1, "amount_cents": 2500 }], // optional
"order_id": "sq_abc", // optional: settle against an existing order
"return_to": "sip:agent@your-eleven-number", // optional: transfer the caller back after payment
"success_message": "You are all set, your order will be ready shortly." // optional (if no return_to)
})
// response (the Checkout Session)
{
"id": "cs_x9f2", // Checkout Session id; the correlation token
"payment_required": "required", // "required" | "optional" | "none"
"transfer_to": "sip:cs_x9f2@transfer.ringup.dev" // where to transfer; embeds the id
}
If payment_required is none, do not transfer: carry on and end the call normally. Otherwise
transfer to transfer_to.
Step 3: Transfer with SIP REFER
Call the transfer_to_number system tool with a SIP-URI destination and the Checkout Session
id as a custom SIP header. The client_message is spoken to the caller as the handoff line,
so write it naturally rather than as a terse “please hold”:
transfer_to_number({
"transferType": "sip_refer",
"transferDestination": { "type": "sip_uri", "sipUri": "{{transfer_to}}" },
"customSipHeaders": { "X-Session-Id": "{{id}}" },
"client_message": "One moment, connecting you to secure payment."
})
Ringup matches the call by the X-Session-Id header, with the id also embedded in the SIP URI
as a backstop, so correlation holds even if a middlebox strips the header.
Use a SIP trunk number and transferType: sip_refer. Custom SIP headers survive to a SIP
endpoint but are stripped on a native-Twilio ElevenLabs number, which would drop the header
match.
Step 4: The return, and the result (the webhook)
Ringup answers the transferred call, recognizes the Caller, charges the saved Card, and texts a
receipt. A first-time caller with no saved card is texted a secure pay link instead. Then the
call ends one of three ways, from what you passed in Step 2:
return_to set: Ringup transfers the caller back to your agent with the outcome in SIP
headers (X-Payment-Status, X-Confirmation), so your agent resumes and closes in its voice.
success_message / failure_message set (no return_to): Ringup reads your line and ends.
- Neither: Ringup reads a short default and ends. The receipt is the proof.
A return_to that cannot connect falls back to reading your success_message (or the default),
so a failed return never strands the caller. Either way, your server reconciles from a webhook:
{
"id": "evt_9f2",
"type": "checkout.completed", // or "checkout.failed"
"data": {
"checkout_id": "cs_x9f2",
"payment_id": "pay_abc",
"amount_cents": 2500,
"order_id": "sq_abc", // echoed back
"confirmation": "2PJJZY",
"caller": "+14155551234",
"card": { "brand": "VISA", "last_4": "5858" }
}
}
Match on the order_id you passed, store the payment_id, and flip your order to paid. See
Webhooks for the full catalog, statuses, signature verification, and
idempotency.
Validation status
ElevenLabs’ transfer_to_number with sip_refer and customSipHeaders is documented by
ElevenLabs. Ringup’s hosted transfer endpoint is rolling out; confirm access in your Ringup
dashboard before relying on this in production. The recognition, charge, receipt, and webhook
behavior is the same payment path Ringup runs everywhere.