Terminal Sessions

Terminal Sessions lets you send charges to a Recurrente POS terminal directly from your system (ERP, custom POS, or any integration). Your system sends a command with the amount, and the terminal automatically displays the payment screen for the customer to pay with their card.

Use cases

  • ERP/POS integration: Your billing or point-of-sale system generates the charge, and the Recurrente terminal processes it.
  • Remote charges: Send charges to terminals at different locations from a centralized system.
  • Automation: Eliminate the need to manually enter amounts on the terminal.

Prerequisites

  • A Recurrente account with API keys
  • At least one POS terminal linked to your account
  • Your terminal ID (found in POS in your dashboard — click on a terminal to see its ID)

Set up the terminal in standby mode

For the terminal to receive commands from your system, it must be on the standby screen. This screen shows “Listo para cobrar” (Ready to charge) and automatically waits for new commands.

  1. In your dashboard, go to POS
  2. Click on the terminal you want to configure
  3. On the terminal detail page, click Modo espera (top-right corner)
  4. The terminal will display the waiting screen

When your system sends a command, the terminal automatically displays the payment screen. After each payment (successful or failed), the terminal returns to standby mode to receive the next command.

In standby mode, the terminal operator cannot manually enter amounts. All charges are controlled from your system via the API.

Integration flow

Your System Recurrente POS Terminal
│ │ │
│ POST /terminal_session_ │ │
│ commands │ │
│ {terminal_id, amount, │ │
│ currency, external_id} │ │
│─────────────────────────────>│ │
│ │ Creates checkout + command │
│ │ (status: pending) │
│ {id, status, checkout_url} │ │
│<─────────────────────────────│ │
│ │ │
│ │ Terminal picks up command │
│ │ (status: dispatched) │
│ │─────────────────────────────>│
│ │ │
│ │ Customer pays │
│ │ with card │
│ │ │
│ Webhook: │ │
│ payment_intent.succeeded │ │
│<─────────────────────────────│ │

1. Create a terminal command

Send a POST to /api/terminal_session_commands with the amount and target terminal.

$curl -X POST https://app.recurrente.com/api/terminal_session_commands \
> -H "X-PUBLIC-KEY: your_public_key" \
> -H "X-SECRET-KEY: your_secret_key" \
> -H "Content-Type: application/json" \
> -d '{
> "terminal_id": "trm_abc123",
> "amount_in_cents": 5000,
> "currency": "GTQ",
> "external_id": "order-1234"
> }'

Response

  • 201 Created — a new command was created
  • 200 OK — an active command with the same external_id already exists (the existing one is returned)

If you receive 200 and the status is dispatched, it means the command was already picked up by the terminal. Use a new external_id to create a fresh command.

1{
2 "id": 42,
3 "external_id": "order-1234",
4 "status": "pending",
5 "terminal_id": "trm_abc123",
6 "amount_in_cents": 5000,
7 "currency": "GTQ",
8 "checkout_id": "ch_xyz789",
9 "checkout_url": "https://app.recurrente.com/checkout-session/ch_xyz789"
10}

Parameters

ParameterTypeRequiredDescription
terminal_idstringYesPOS terminal ID
amount_in_centsintegerYes*Amount in cents (e.g. 5000 = Q50.00)
amountnumberYes*Amount in units (e.g. 50.00). Alternative to amount_in_cents
currencystringYesCurrency: GTQ or USD
external_idstringYesYour system’s ID for this charge (used for idempotency)

Send amount_in_cents or amount, not both. If you send amount, it is automatically converted to cents.

2. Receive the payment result

Register a webhook endpoint to receive notifications when the payment completes.

Listen for the payment_intent.succeeded event:

1{
2 "event": "payment_intent.succeeded",
3 "data": {
4 "checkout": {
5 "id": "ch_xyz789",
6 "metadata": {
7 "source": "terminal_session_webhook",
8 "terminal_id": "trm_abc123",
9 "external_id": "order-1234"
10 }
11 },
12 "amount_in_cents": 5000,
13 "currency": "GTQ"
14 }
15}

Use the metadata.external_id field to match the payment back to the order in your system.

Command statuses

StatusDescription
pendingCreated, waiting for the terminal to pick it up
dispatchedTerminal received the command and is processing the charge
consumedCharge completed successfully
supersededReplaced by a newer command to the same terminal
failedCharge failed

Idempotency

The external_id is unique per account. If you send the same external_id twice:

  • If the previous command is active (pending or dispatched), the existing command is returned.
  • If the previous command was superseded or failed, the existing command is returned without creating a new one.

This lets you safely retry requests without creating duplicate charges.

Superseding (command replacement)

If you send a new command to the same terminal with a different external_id, all previous pending commands for that terminal are marked as superseded. The terminal only processes the most recent command.

This is useful when:

  • The customer changes the order amount
  • You need to cancel a charge and send a new one
  • Your system retries with a new order ID

Full example

1import requests
2
3API_URL = "https://app.recurrente.com/api"
4HEADERS = {
5 "X-PUBLIC-KEY": "your_public_key",
6 "X-SECRET-KEY": "your_secret_key",
7 "Content-Type": "application/json"
8}
9
10def send_to_terminal(terminal_id, amount, currency, order_id):
11 """Send a charge to a POS terminal."""
12 response = requests.post(
13 f"{API_URL}/terminal_session_commands",
14 headers=HEADERS,
15 json={
16 "terminal_id": terminal_id,
17 "amount": amount,
18 "currency": currency,
19 "external_id": order_id
20 }
21 )
22 return response.json()
23
24# Send a Q150.00 charge to the terminal
25result = send_to_terminal("trm_abc123", 150.00, "GTQ", "order-5678")
26print(f"Command created: {result['status']}")