Payments API
Initiate, track, cancel, refund, and void terminal payments
The Terminal Payments API lets you initiate card-present payments on a terminal device and manage the full payment lifecycle.
Payment Lifecycle
requested → in_progress → approved / declined / error / timed_out
→ cancelled (if cancelled before completion)| Status | Description |
|---|---|
requested | Payment command sent to terminal. Waiting for card presentation. |
in_progress | Terminal is processing the transaction. |
approved | Payment succeeded. Card was charged. |
declined | Card was declined by the issuer or processor. |
cancelled | Payment was cancelled before the customer presented their card. |
error | Terminal encountered an error (hardware failure, connection issue). |
timed_out | Terminal did not respond within 3 minutes. |
Create Payment
Send a payment command to a specific terminal device. The payment is processed asynchronously -- the API returns immediately with a requested status, and you receive the result via webhook or by polling.
const payment = await revkeen.terminalPayments.create({
device_id: 'd1e2f3a4-b5c6-7890-abcd-ef1234567890',
amount_minor: 5000, // £50.00
currency: 'GBP',
invoice_id: 'inv_xxxxxxxx', // Optional
reference: 'walk-in-sale-001', // Optional
});
console.log(payment.data.id); // Terminal payment attempt ID
console.log(payment.data.status); // "requested"payment = client.terminal_payments.create(
device_id="d1e2f3a4-b5c6-7890-abcd-ef1234567890",
amount_minor=5000,
currency="GBP",
invoice_id="inv_xxxxxxxx",
reference="walk-in-sale-001"
)
print(payment.data.id)
print(payment.data.status) # "requested"curl -X POST "https://api.revkeen.com/v2/terminal-payments" \
-H "x-api-key: rk_live_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '{
"device_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
"amount_minor": 5000,
"currency": "GBP",
"invoice_id": "inv_xxxxxxxx",
"reference": "walk-in-sale-001"
}'Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
device_id | UUID | Yes | Target terminal device. Call GET /v2/terminal-devices to discover available devices. |
amount_minor | integer | Yes | Amount in minor units (e.g., pence for GBP, cents for USD). |
currency | string | Yes | ISO 4217 currency code (e.g., GBP, USD). |
invoice_id | UUID | No | Invoice to associate with the payment. Omit for walk-in or ad-hoc payments. |
reference | string | No | Custom reference for the payment. Auto-generated if not provided. |
You must always specify a device_id. There is no auto-routing -- even merchants with a single terminal must pass the device ID explicitly. Call GET /v2/terminal-devices first to discover available devices.
List Payments
Retrieve terminal payments with optional filters.
// List all approved terminal payments for an invoice
const payments = await revkeen.terminalPayments.list({
invoice_id: 'inv_xxxxxxxx',
status: 'approved',
});
// List all terminal payments on a specific device
const devicePayments = await revkeen.terminalPayments.list({
device_id: 'd1e2f3a4-b5c6-7890-abcd-ef1234567890',
limit: 50,
});curl -X GET "https://api.revkeen.com/v2/terminal-payments?status=approved&limit=50" \
-H "x-api-key: rk_live_your_api_key"Retrieve a Payment
const payment = await revkeen.terminalPayments.retrieve('tpa_xxxxxxxx');
if (payment.data.status === 'approved') {
console.log('Auth code:', payment.data.auth_code);
console.log('Card:', payment.data.card_scheme, payment.data.masked_pan);
console.log('Entry mode:', payment.data.entry_mode);
}curl -X GET "https://api.revkeen.com/v2/terminal-payments/tpa_xxxxxxxx" \
-H "x-api-key: rk_live_your_api_key"Cancel a Payment
Cancel a payment that is still waiting for card presentation (status requested or in_progress):
await revkeen.terminalPayments.cancel('tpa_xxxxxxxx');curl -X POST "https://api.revkeen.com/v2/terminal-payments/tpa_xxxxxxxx/cancel" \
-H "x-api-key: rk_live_your_api_key"Refund a Payment
Refund a completed terminal payment. The refund command is sent to the same terminal:
// Full refund
await revkeen.terminalPayments.refund('tpa_xxxxxxxx');
// Partial refund
await revkeen.terminalPayments.refund('tpa_xxxxxxxx', {
amount_minor: 2500, // Refund £25.00 of the original amount
reason: 'Customer return',
});# Full refund
client.terminal_payments.refund("tpa_xxxxxxxx")
# Partial refund
client.terminal_payments.refund("tpa_xxxxxxxx",
amount_minor=2500,
reason="Customer return"
)Void a Payment
Void a completed terminal payment before settlement:
await revkeen.terminalPayments.void('tpa_xxxxxxxx', {
reason: 'Duplicate charge',
});Response Shape
All terminal payment endpoints return a consistent response:
{
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"invoice_id": "inv_xxxxxxxx",
"device_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
"type": "sale",
"status": "approved",
"amount_minor": 5000,
"currency": "GBP",
"reference": "walk-in-sale-001",
"terminal_serial": "21032100001",
"uti": "900012345678901234567890",
"auth_code": "123456",
"response_code": "00",
"rrn": "123456789012",
"card_scheme": "VISA",
"masked_pan": "************1234",
"entry_mode": "contactless",
"error_message": null,
"created_at": "2026-03-11T10:00:00Z",
"completed_at": "2026-03-11T10:00:15Z"
}
}Response Fields
| Field | Description |
|---|---|
type | sale, refund, or void |
terminal_serial | Serial number of the PAX terminal that processed the payment |
uti | Unique Transaction Identifier from the terminal |
auth_code | Authorization code from the payment processor |
response_code | Terminal response code (00 = approved) |
rrn | Retrieval Reference Number for settlement matching |
card_scheme | Card network (VISA, MASTERCARD, AMEX, etc.) |
masked_pan | Masked card number (only last 4 digits visible) |
entry_mode | How the card was read: contactless, emv_chip, magnetic_stripe, manual_entry, or fallback |
Concurrency and Per-Device Locking
RevKeen enforces a per-device concurrency lock. Only one payment can be in progress on a given terminal at a time. If you attempt to initiate a second payment on a device that is already processing, the API returns a 409 Conflict:
{
"error": "device_busy",
"message": "Another payment is in progress on this terminal"
}Wait for the current payment to complete (or cancel it) before sending a new one.
Error Codes
| HTTP Status | Error Code | Meaning |
|---|---|---|
201 | - | Payment initiated successfully |
400 | validation_error | Invalid request parameters |
404 | device_not_found | Device ID does not exist or does not belong to this merchant |
409 | device_busy | Another payment is already in progress on this device |
422 | device_offline | Device is not online or heartbeat is stale |
422 | device_not_paired | Device connector is registered but terminal is not paired |
403 | insufficient_scopes | API key lacks terminal:write or terminal:read scope |
Idempotency
All terminal payment POST endpoints support idempotency. Include the Idempotency-Key header to safely retry requests:
curl -X POST "https://api.revkeen.com/v2/terminal-payments" \
-H "Idempotency-Key: unique-request-id-123" \
-H "x-api-key: rk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "device_id": "...", "amount_minor": 5000, "currency": "GBP" }'Sending the same Idempotency-Key returns the cached response from the first request. Keys are valid for 24 hours.
Related
- Devices API -- Query available devices
- Webhook Events -- Receive payment results asynchronously
- Best Practices -- Idempotency, error handling, testing