Skip to main content

Step-by-step guide

A quick example of a Terminals flow.

For ease of use, the request samples here will be cURL. The response samples are JSON. The language you use for your own integration may differ, and throughout Dojo's documentation there are code samples in PHP, cURL, Python, and other common languages and formats.

Depending upon the sessionType you choose, perform the following steps for a successful terminal session:

Payment Intent Flow

Flow diagram: Terminal Session — Sale

sequenceDiagram
actor U as User
participant T as Dojo Terminal
participant E as EPOS system
participant B as dojo backend

U->>E: Places an order
E->>B: POST /payment-intents (Sends payment information)
B-->>E: Payment intent object
E->>B: GET /terminals?statuses=Available (Requests for available terminals)
B-->>E: List of available terminals
E->>B: POST /terminal-sessions (details: sale)
B-->>E: Terminal session object with amount to capture
E->>T: Displays amount to capture
U->>T: Presents card
T->>B: Processes payment
loop Poll for notifications
E->>B: GET /terminal-sessions/{terminalSessionId}
B-->>E: Terminal session object with status
end
T->>B: Sends response with the payment result
B-->>E: Sends response with the payment result
T-->>U: Print device receipt
E-->>U: Print order receipt
E->>B: GET /payment-intents/{paymentIntentId}
B-->>E: Payment intent object
note

An unlinkedRefund refund session type contains no reference to a payment intent and can allow any amount to be refunded directly from the EPOS systems.

LLM walkthrough: complete Pay at Counter happy path

Use this as the canonical PAC template when your POS or middleware owns the basket, takes a card-present payment on a Dojo terminal, and later needs to issue a refund.

All Dojo REST calls in this walkthrough use:

  • Authorization: Basic <api_key> (literal Basic prefix; do not base64-encode api_key:)
  • version: 2026-02-27
  • Content-Type: application/json
  • https://api.dojo.tech for both sandbox and production; the API key determines the environment
  • software-house-id: softwareHouse1 and reseller-id: reseller1 on terminal endpoints in sandbox; production integrations receive assigned values from Dojo

Minimal end-to-end example (bash + curl)

This sample auto-accepts a signature request for demonstration purposes. In production, replace that with the operator's actual decision.

API_KEY="sk_sandbox_your_key"
BASE_URL="https://api.dojo.tech"
VERSION="2026-02-27"
COMMON_HEADERS=(-H "Authorization: Basic $API_KEY" -H "version: $VERSION" -H "Content-Type: application/json" -H "Accept: application/json")
TERMINAL_HEADERS=("${COMMON_HEADERS[@]}" -H "software-house-id: softwareHouse1" -H "reseller-id: reseller1")

PAYMENT_INTENT_ID=$(
curl -sS "$BASE_URL/payment-intents" "${COMMON_HEADERS[@]}" \
-d '{"amount":{"value":1000,"currencyCode":"GBP"},"reference":"Order-234","captureMode":"Auto"}' \
| jq -r '.id'
)

TERMINAL_ID=$(
curl -sS "$BASE_URL/terminals?statuses=Available" "${TERMINAL_HEADERS[@]}" \
| jq -r '.[0].id'
)

SESSION_ID=$(
curl -sS "$BASE_URL/terminal-sessions" "${TERMINAL_HEADERS[@]}" \
-d "{\"terminalId\":\"$TERMINAL_ID\",\"details\":{\"sessionType\":\"Sale\",\"sale\":{\"paymentIntentId\":\"$PAYMENT_INTENT_ID\"}}}" \
| jq -r '.id'
)

while :; do
SESSION=$(curl -sS "$BASE_URL/terminal-sessions/$SESSION_ID" "${TERMINAL_HEADERS[@]}")
STATUS=$(jq -r '.status' <<<"$SESSION")
case "$STATUS" in
InitiateRequested|Initiated|CancelRequested) sleep 1 ;;
SignatureVerificationRequired)
curl -sS -X PUT "$BASE_URL/terminal-sessions/$SESSION_ID/signature" "${TERMINAL_HEADERS[@]}" \
-d '{"accepted":true}' >/dev/null
sleep 1
;;
Authorized|Captured|SignatureVerificationAccepted|SignatureVerificationRejected|Canceled|Declined|Expired)
break
;;
*) echo "Unexpected terminal session status: $STATUS" >&2; break ;;
esac
done

curl -sS "$BASE_URL/payment-intents/$PAYMENT_INTENT_ID" "${COMMON_HEADERS[@]}" | jq

1. Create the payment intent

POST /payment-intents

Request body

{
"amount": {
"value": 1000,
"currencyCode": "GBP"
},
"reference": "Order-234",
"description": "Card terminal sale for table 12",
"captureMode": "Auto"
}

Response body

{
"id": "pi_sandbox_G_FeegU8WESxtY_Nct8jqw",
"captureMode": "Auto",
"status": "Created",
"paymentMethods": [
"Card"
],
"amount": {
"value": 1000,
"currencyCode": "GBP"
},
"totalAmount": {
"value": 1000,
"currencyCode": "GBP"
},
"refundedAmount": 0,
"createdAt": "2024-04-07T23:25:44.802258Z",
"updatedAt": "2024-04-07T23:25:44.802258Z",
"reference": "Order-234",
"description": "Card terminal sale for table 12",
"paymentLink": "https://pay.dojo.tech/checkout/pi_sandbox_G_FeegU8WESxtY_Nct8jqw",
"paymentEvents": [],
"terminalSessionHistory": []
}

2. Create the terminal session

POST /terminal-sessions

Request body

{
"terminalId": "tm_sandbox_65c5fe8a104a1222b2d8b968",
"details": {
"sale": {
"paymentIntentId": "pi_sandbox_G_FeegU8WESxtY_Nct8jqw"
},
"sessionType": "Sale"
}
}

Response body

{
"createdAt": "2024-02-15T00:48:10.756Z",
"details": {
"sale": {
"paymentIntentId": "pi_sandbox_G_FeegU8WESxtY_Nct8jqw"
},
"sessionType": "Sale"
},
"expireAt": "2024-02-15T00:48:20.756Z",
"id": "ts_sandbox_65cd5f4a57ba13ad7424acd0",
"notificationEvents": [],
"status": "InitiateRequested",
"statusEvents": [
{
"createdAt": "2024-02-15T00:48:10.756Z",
"debugMessage": "",
"status": "InitiateRequested"
}
],
"terminalId": "tm_sandbox_65c5fe8a104a1222b2d8b968",
"updatedAt": "2024-02-15T00:48:10.756Z"
}

3. Poll the terminal session until it reaches a terminal state

GET /terminal-sessions/ts_sandbox_65cd5f4a57ba13ad7424acd0

Repeat this lookup while status is still in flight (InitiateRequested, Initiated, or CancelRequested).

For a sale flow, the next meaningful outcome will be one of:

  • Captured for an auto-capture sale
  • Authorized for a manual-capture sale that still needs a later capture request
  • SignatureVerificationRequired when you must call PUT /terminal-sessions/{terminalSessionId}/signature
  • Canceled, Declined, or Expired when the session cannot complete successfully

Refunded and Reversed are payment-intent states, not terminal-session states.

Response body

{
"createdAt": "2024-02-15T00:48:10.756Z",
"details": {
"sale": {
"paymentIntentId": "pi_sandbox_G_FeegU8WESxtY_Nct8jqw"
},
"sessionType": "Sale"
},
"expireAt": "2024-02-15T00:49:40.756Z",
"id": "ts_sandbox_65cd5f4a57ba13ad7424acd0",
"notificationEvents": [
{
"createdAt": "2024-02-15T00:48:11.980Z",
"notificationType": "PresentCard"
},
{
"createdAt": "2024-02-15T00:48:16.066Z",
"notificationType": "PleaseWait"
},
{
"createdAt": "2024-02-15T00:48:16.176Z",
"notificationType": "EnterPin"
},
{
"createdAt": "2024-02-15T00:48:18.354Z",
"notificationType": "RemoveCard"
}
],
"status": "Captured",
"statusEvents": [
{
"createdAt": "2024-02-15T00:48:10.756Z",
"debugMessage": "",
"status": "InitiateRequested"
},
{
"createdAt": "2024-02-15T00:48:10.972Z",
"debugMessage": "",
"status": "Initiated"
},
{
"createdAt": "2024-02-15T00:48:19.448Z",
"debugMessage": "",
"status": "Captured"
}
],
"terminalId": "tm_sandbox_65c5fe8a104a1222b2d8b968",
"updatedAt": "2024-02-15T00:48:19.448Z"
}

4. Handle the successful payment notification and fetch the final payment intent

Use the webhook as the asynchronous confirmation, then read the payment intent for the full card-present result.

Webhook payload

{
"id": "evt_pac_01HPC2KQY7P5LZ5Q2B4W0A1B2C",
"event": "payment_intent.status_updated",
"accountId": "acc_test",
"createdAt": "2024-02-15T00:48:19.448Z",
"data": {
"paymentIntentId": "pi_sandbox_G_FeegU8WESxtY_Nct8jqw",
"paymentStatus": "Captured",
"captureMode": "Auto"
}
}

GET /payment-intents/pi_sandbox_G_FeegU8WESxtY_Nct8jqw

Response body

{
"id": "pi_sandbox_G_FeegU8WESxtY_Nct8jqw",
"captureMode": "Auto",
"status": "Captured",
"paymentMethods": [
"Card"
],
"amount": {
"value": 1000,
"currencyCode": "GBP"
},
"totalAmount": {
"value": 1000,
"currencyCode": "GBP"
},
"refundedAmount": 0,
"reference": "Order-234",
"description": "Card terminal sale for table 12",
"paymentDetails": {
"transactionId": "19e4535e-ef6e-48e2-90be-d2b313c1cefd",
"transactionDateTime": "2024-07-25T08:49:20.973410318Z",
"message": "DEPOSITED",
"authCode": "123456",
"card": {
"cardNumber": "44565300****1096",
"cardName": "test",
"expiryDate": "2024-12-31",
"cardType": "VISA",
"entryMode": "Contactless",
"verificationMethod": "Pin"
}
},
"paymentEvents": [
{
"transactionId": "19e4535e-ef6e-48e2-90be-d2b313c1cefd",
"transactionDateTime": "2024-07-25T08:49:20.973410318Z",
"eventType": "Captured",
"authCode": "123456",
"cardNumber": "44565300****1096",
"expiryDate": "2024-12-31",
"cardType": "VISA",
"cardholderName": "test",
"paymentMethodId": ""
}
]
}

5. Refund the payment later with the Payment Intents API

Use the stored paymentIntentId for server-side refunds when the customer is no longer present.

POST /payment-intents/pi_sandbox_G_FeegU8WESxtY_Nct8jqw/refunds

Request body

{
"amount": 1000,
"refundReason": "Demo refund",
"notes": "Customer returned the order in store"
}

Response body

{
"message": "refund for the order 3443",
"refundId": "rfnd_g8mCx87TykeQ6BOXqxZ9NQ"
}

For a card-present refund on the terminal instead of a server-side API refund, create a matchedRefund terminal session and reuse the original paymentIntentId.

Step 1. Create a payment intent

First, make sure you've already created a payment intent. Visit the payment intents documentation for more information about configuration options. This is where important information like payment details, amount, and currency are stored.

Step 2: Check which terminals are available

GET /terminals

Main page: Retrieve all terminals

You can check which terminals are available by requesting a list of all terminals, or by requesting a filtered list of Available terminals.

Step 3: Create a terminal session

POST /terminal-sessions

Main page: Create session

Over the course of a single business day, a terminal might be associated with hundreds of sessions.

In your request, determine the terminal sessionType. Make sure you've already created your payment intent before creating a terminal session. You will have to specify the paymentIntentId in the request for Sale and MatchedRefund sessions. Dojo will initiate the payment intent automatically once you've linked them.

A session will end once the payment is complete, or once the refund has been fully processed.

(Optional) Step 4: Cancel a terminal session

PUT /terminal-sessions/{terminalSessionId}/cancel

curl --location --request PUT 'https://api.dojo.tech/master/terminal-sessions/ts_sandbox_65af01524a36e6a14356dbc0/cancel

Main page: Cancel session

You can cancel a terminal session as long as no card has been presented, inserted or swiped. If payment has been attempted or completed on a terminal session, it will no longer be possible to cancel it.

(Optional) Step 5: Respond to a signature verification request

PUT /terminal-sessions/{terminalSessionId}/signature

Main page: Signature verification

tip

You'll only need to complete this step if the terminal session status changes to SignatureVerificationRequired.

Respond to a signature verification request with true (to accept it) or false (to decline it). If you accept signature verification and send a request with the accepted field set to true, you will receive a detailed response object including both a customerReceipt and a merchantReceipt.

Step 6: Retrieve the original payment intent

Now that the customer has completed the payment on the terminal and the terminal has reached a final state, retrieve the original payment intent to ensure that the payment is complete.

GET /payment-intents/{paymentIntentId}