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:
- Sale
- Matched refund
- Unlinked refund

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

Flow diagram: Session Type Matched Refund
sequenceDiagram
actor U as User
participant T as Dojo Terminal
participant E as EPOS system
participant B as dojo backend
U->>E: Requests for a refund against a previous transaction
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: matchedRefund)
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 refund transaction result
B-->>E: Sends response with the refund result
T-->>U: Print device receipt
E-->>U: Print order receipt
E->>B: GET /payment-intents/{paymentIntentId}
B-->>E: Payment intent object

Flow diagram: Session Type Unlinked Refund
sequenceDiagram
actor U as User
participant T as Dojo Terminal
participant E as EPOS system
participant B as dojo backend
U->>E: Requests refund for a custom amount
E->>B: GET /terminals?statuses=Available (Requests for available terminals)
B-->>E: List of available terminals
E->>B: POST /terminal-sessions (details: unlinkedRefund)
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 refund transaction result
B-->>E: Sends response with the refund result
T-->>U: Print device receipt
E-->>U: Print order receipt
E->>B: GET /payment-intents/{paymentIntentId}
B-->>E: Payment intent object
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>(literalBasicprefix; do not base64-encodeapi_key:)version: 2026-02-27Content-Type: application/jsonhttps://api.dojo.techfor both sandbox and production; the API key determines the environmentsoftware-house-id: softwareHouse1andreseller-id: reseller1on 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:
Capturedfor an auto-capture saleAuthorizedfor a manual-capture sale that still needs a later capture requestSignatureVerificationRequiredwhen you must call PUT/terminal-sessions/{terminalSessionId}/signatureCanceled,Declined, orExpiredwhen 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
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}