PaymentsIn-Person PaymentsCloud API

Cloud API

Drive VINR terminals from your server through VINR's cloud — synchronous and asynchronous modes.

View as MarkdownInstall skills

Use the Cloud API when your backend needs to instruct a terminal without direct network access to the device. Your server calls the VINR API, which routes commands to the terminal in real time over a persistent TLS connection. No local networking or certificate management required.

Choose Cloud API when:

  • Your POS logic runs on a server or in the cloud rather than on-site
  • You want the simplest integration path — one POST from any language
  • You need centralized fleet control: cancel or redirect any terminal from a single API call

For sub-50 ms screen activation or offline resilience, see Local API. To attach an existing ECR or POS system, see ECR Integration.

How it worksAsk

Your server ──POST──▶ VINR API ──▶ Terminal (persistent TLS)

                                   Cardholder interaction

                      VINR API ◀──────── Terminal

           webhook event ─┘

                     Your server
  1. Your backend creates a terminal payment via the VINR API, specifying the target terminal ID.
  2. VINR routes the command to the terminal. The terminal screen activates within 200–400 ms.
  3. The cardholder presents their card. The terminal handles the interaction and routes authorization to the card network.
  4. VINR updates the payment object and delivers the result either inline (synchronous mode) or as a webhook event (asynchronous mode).

PrerequisitesAsk

  • A VINR secret key (sk_live_... for production, sk_test_... for sandbox).
  • At least one terminal registered and activated in your account.
  • Outbound HTTPS from your server to *.vinr.com on port 443.

AuthenticationAsk

Every Cloud API request uses your secret key as a Bearer token:

Authorization: Bearer sk_live_xxxxxxxxxxxx
Content-Type: application/json

Never include your secret key in client-side code, mobile apps, or browser JavaScript.

Create a paymentAsk

POST https://api.vinr.com/v1/terminal/payments
import { Vinr } from '@vinr/sdk';

const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });

const payment = await vinr.terminal.payments.create({
  terminalId: 'term_01HZ5QXYZ',
  amount: 2500,
  currency: 'USD',
  reference: 'order_8821',
});
import vinr, os

client = vinr.Client(secret_key=os.environ['VINR_SECRET_KEY'])

payment = client.terminal.payments.create(
    terminal_id='term_01HZ5QXYZ',
    amount=2500,
    currency='USD',
    reference='order_8821',
)
curl https://api.vinr.com/v1/terminal/payments \
  -H "Authorization: Bearer $VINR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "terminalId": "term_01HZ5QXYZ",
    "amount": 2500,
    "currency": "USD",
    "reference": "order_8821"
  }'

Prop

Type

Response modesAsk

Synchronous

Pass responseMode: 'sync' (or call .createSync()). Your server holds the HTTPS connection open while the cardholder interacts with the terminal. The final payment object — including card brand, last4, and auth code — is returned inline when the transaction finishes.

const payment = await vinr.terminal.payments.createSync({
  terminalId: 'term_01HZ5QXYZ',
  amount: 2500,
  currency: 'USD',
  reference: 'order_8821',
});

// status is 'completed' or 'failed' — no webhook needed for the result
console.log(payment.status, payment.last4);
curl https://api.vinr.com/v1/terminal/payments \
  -H "Authorization: Bearer $VINR_SECRET_KEY" \
  -H "Content-Type: application/json" \
  --max-time 150 \
  -d '{
    "terminalId": "term_01HZ5QXYZ",
    "amount": 2500,
    "currency": "USD",
    "reference": "order_8821",
    "responseMode": "sync"
  }'

Set your HTTP client timeout to at least 150 seconds in sync mode. Card interactions typically complete in 10–30 seconds but can take up to 90 seconds if the customer takes time entering a PIN. A premature client-side timeout leaves the terminal in a pending state — call Retrieve a payment immediately to find the outcome before retrying.

Approved response:

{
  "id": "tpay_01HZ5QA7BK",
  "terminalId": "term_01HZ5QXYZ",
  "amount": 2500,
  "amountCaptured": 2500,
  "currency": "USD",
  "reference": "order_8821",
  "status": "completed",
  "captureMethod": "automatic",
  "responseMode": "sync",
  "entryMethod": "contactless",
  "last4": "4242",
  "brand": "visa",
  "authCode": "A12345",
  "createdAt": "2026-06-11T09:00:00Z",
  "completedAt": "2026-06-11T09:00:12Z"
}

Declined response:

{
  "id": "tpay_01HZ5QA8EF",
  "terminalId": "term_01HZ5QXYZ",
  "amount": 2500,
  "currency": "USD",
  "reference": "order_8821",
  "status": "failed",
  "responseMode": "sync",
  "declineCode": "card_declined",
  "createdAt": "2026-06-11T09:01:00Z",
  "failedAt": "2026-06-11T09:01:09Z"
}

Best for: attended POS counters and kiosks where the UI must advance only after a confirmed outcome.

Asynchronous

The default mode. The API returns 202 Accepted immediately with the payment object at status: 'pending'. The terminal result arrives as a webhook event.

{
  "id": "tpay_01HZ5QA7BK",
  "terminalId": "term_01HZ5QXYZ",
  "amount": 2500,
  "currency": "USD",
  "reference": "order_8821",
  "status": "pending",
  "responseMode": "async",
  "createdAt": "2026-06-11T09:00:00Z"
}

Your webhook handler receives terminal_payment.completed or terminal_payment.failed when the card interaction finishes. See Webhooks for event shapes and signature verification.

Best for: unattended kiosks, background queues, and server-side flows that process results out of band.

Payment status lifecycleAsk

pending → processing → completed
                    ↘ failed
                    ↘ cancelled
StatusMeaning
pendingTerminal received the session and is displaying the payment prompt
processingCard presented; authorization request in flight to the issuer
completedApproved; funds captured (or authorized, for manual-capture payments)
failedDeclined or error — check declineCode
cancelledCancelled before card presentation, or terminal prompt timed out

Cancel a paymentAsk

Cancel an active session before the cardholder presents a card. Only possible while status is pending.

await vinr.terminal.payments.cancel('tpay_01HZ5QA7BK');
curl -X POST https://api.vinr.com/v1/terminal/payments/tpay_01HZ5QA7BK/cancel \
  -H "Authorization: Bearer $VINR_SECRET_KEY"

Once the payment reaches processing (card has been presented), cancellation is rejected — the authorization is already in flight. Wait for the result and issue a refund if needed.

Retrieve a paymentAsk

Fetch the current state of a payment. Use this as the recovery step after a sync-mode HTTP timeout, or when a webhook event was not received.

const payment = await vinr.terminal.payments.retrieve('tpay_01HZ5QA7BK');
console.log(payment.status);
curl https://api.vinr.com/v1/terminal/payments/tpay_01HZ5QA7BK \
  -H "Authorization: Bearer $VINR_SECRET_KEY"

Do not poll this endpoint in a tight loop as a substitute for webhooks. Use it only as a one-time fallback after a missed event. Sustained polling is rate-limited.

Manual captureAsk

Use captureMethod: 'manual' when the final amount is not known at card presentation — for example, a restaurant table that adds a tip after the meal.

// Step 1 — authorize at the table
const payment = await vinr.terminal.payments.create({
  terminalId: 'term_01HZ5QXYZ',
  amount: 2500,
  currency: 'USD',
  reference: 'table_12',
  captureMethod: 'manual',
});

// Step 2 — capture with final amount after tip is added
await vinr.terminal.payments.capture(payment.id, {
  amount: 2875, // original + tip
});

After card interaction the payment sits at status: 'authorized'. Call capture to move funds, optionally adjusting the amount upward within your permitted tolerance. Uncaptured authorizations expire after 7 days.

Error handlingAsk

declineCodeCauseRecommended action
terminal_not_foundTerminal ID not registered in your accountVerify the ID in the Dashboard
terminal_offlineTerminal has not checked in for ≥ 60 sCheck device power and connectivity; retry after reconnection
terminal_busyAnother session is active on this terminalWait for the active session to complete, or cancel it first
card_declinedGeneric issuer declineAsk the customer to try a different card
insufficient_fundsCard balance too lowAsk the customer to try a different card
expired_cardCard past its expiry dateAsk the customer for a current card
lost_or_stolenCard flagged by the card networkDo not retry; follow your security policy
communication_errorTerminal lost connectivity during processingRetrieve the payment to confirm the outcome; cancel and retry if still pending

TestingAsk

Use a sk_test_... key and the reserved sandbox terminal ID term_test_simulator to test without physical hardware.

const vinr = new Vinr({ secretKey: 'sk_test_...' });

const payment = await vinr.terminal.payments.create({
  terminalId: 'term_test_simulator',
  amount: 1000,
  currency: 'USD',
  reference: 'sandbox_order_001',
});

Drive specific outcomes with test card numbers:

Card numberOutcome
4242 4242 4242 4242Approved
4000 0000 0000 0002card_declined
4000 0000 0000 9995insufficient_funds
4000 0000 0000 0069expired_card
4100 0000 0000 0019lost_or_stolen

Append ?simulateDelay=30 to the create URL to simulate a 30-second interaction, useful for testing sync-mode timeout handling.

Next stepsAsk

Was this page helpful?
Edit on GitHub

Last updated on

On this page