Printing on SmartPOS terminal
The Print API gives you full control over the thermal printer built into Kushki ONE terminals. Print any content from your POS system — before, during or after a transaction, or with no transaction at all — without installing drivers, SDKs or configuring hardware on your side.
Requirements
How it works
The print cycle has two parts: a sync request that queues the job, and an async notification that tells you when it finished.
- Your POS system builds a
commandsarray with the receipt layout. - You send the job creation request. The terminal answers
202 Acceptedright away. - Printing runs asynchronously on the hardware.
- You learn the final result over webhook or by querying the status.
The payloads and the headers are identical across topologies. The routes are not: the base URL stops at the terminal, and each route carries its own prefix.
| Topology | Base URL |
|---|---|
| Local Network (LAN / Wi-Fi) | http://{TERMINAL_IP}:6868/terminal/v1 or https://{TERMINAL_IP}:6869/terminal/v1 |
| Cloud (Internet) — UAT | https://uat-cloudt.kushkipagos.com/terminal/v1/{terminalSerial} |
| Cloud (Internet) — Production | https://cloudt.kushkipagos.com/terminal/v1/{terminalSerial} |
Authentication
Printing uses the same mechanism as the payment operations — hash + encryption, the only one Kushki ONE has:
| Element | Value |
|---|---|
Authorization | Basic followed by the SHA-512 hash. The Basic prefix is required |
timestamp | Unix timestamp in seconds, within ±5 minutes of server time |
| Body | The encrypted envelope {"data": "<iv_hex>:<ciphertext_hex>"} |
The commands array shown on this page is the plaintext you encrypt, not what travels on the wire. The full flow is on Authentication and request encryption.
Use cases
The API is not limited to payment receipts. Any content your business needs to hand over on paper can be triggered from your POS system.
| Use case | Description |
|---|---|
| Payment receipt | Direct sale, pre-authorization capture, refund or void |
| Discount coupon | A code for the customer’s next purchase |
| QR code | Wi-Fi password, loyalty link, digital receipt, product information |
| Loyalty and promotions | Point balance, reward tiers, special offers |
| Pre-check or order summary | Kitchen ticket or table summary before the final charge |
| Reversal record | A printed record of a cancellation or refund |
| Reprint | Print a previous receipt again with the same printJobId |
| Free content | Text, image, QR or barcode, with no transaction involved |
Anatomy of a receipt
Each visual section of the receipt maps to a command type inside the commands array.
Request structure
| Field | Type | Required | Description |
|---|---|---|---|
commands | array | ✅ | Ordered list of print commands |
printJobId | string | — | Idempotency key. If you leave it out, a UUID is generated |
externalReference | string | — | Your own free-form reference, for example Table-14 |
webhookUrl | string | — | URL that will receive the result when the job finishes |
skipIfBusy | boolean | — | With true, returns 409 immediately if the queue is busy. Defaults to false |
Command types
| Type | Description |
|---|---|
text | A line of text with size, alignment, bold, italic and underline |
columns | A multi-column row with proportional widths, ideal for product and price |
divider | A full-width separator line: SOLID, DOTTED or EMPTY |
feed | Advances the paper N blank lines |
space | Inserts precise vertical space in pixels |
cut | Triggers the cutter. Safely ignored on terminals without one |
image | Prints a PNG or JPG image in Base64. Use algorithm: BINARIZATION for logos |
qr | Generates a QR code on the printer hardware |
barcode | Generates a CODE128 barcode on the hardware |
Full example
This request builds a receipt with a logo, header, line items, total, QR code and an automatic cut.
{"printJobId": "TICKET-190209","externalReference": "Table-14","webhookUrl": "https://api.yourbusiness.com/webhook/print-events","skipIfBusy": false,"commands": [{ "type": "image", "base64Image": "iVBORw0KGgoAAAANSUhEUg...", "align": "CENTER", "width": 300, "algorithm": "BINARIZATION" },{ "type": "text", "text": "EL BUEN SABOR RESTAURANT\n", "align": "CENTER", "size": 32, "bold": true },{ "type": "text", "text": "Tax ID: 900.123.456-7\n", "align": "CENTER", "size": 22 },{ "type": "divider", "dividerType": "DOTTED" },{ "type": "columns", "columns": [{ "text": "2x Burger Combo", "weight": 2, "align": "LEFT" },{ "text": "30,000.00 COP", "weight": 1, "align": "RIGHT" } ] },{ "type": "columns", "columns": [{ "text": "1x Fresh Juice", "weight": 2, "align": "LEFT" },{ "text": "8,000.00 COP", "weight": 1, "align": "RIGHT" } ] },{ "type": "divider", "dividerType": "SOLID" },{ "type": "columns", "columns": [{ "text": "TOTAL", "weight": 2, "align": "LEFT" },{ "text": "38,000.00 COP", "weight": 1, "align": "RIGHT" } ] },{ "type": "qr", "content": "https://yourbusiness.com/receipt/TICKET-190209", "dotSize": 6, "errorLevel": "M", "align": "CENTER" },{ "type": "feed", "lines": 3 },{ "type": "cut" }]}
Send the request from your back-end:
- Javascript
- Python
const res = await fetch(`${BASE_URL}/print`, {method: "POST",headers: buildHeaders(payload), // Authorization + timestampbody: JSON.stringify(payload),});const job = await res.json();console.log(res.status, job.printJobId, job.status);// 202 TICKET-190209 PENDING
import requestsres = requests.post(f"{BASE_URL}/print",headers=build_headers(payload), json=payload)job = res.json()print(res.status_code, job["printJobId"], job["status"])# 202 TICKET-190209 PENDING
The terminal answers immediately:
{"printJobId": "TICKET-190209","status": "PENDING","message": "Impresión encolada correctamente"}
| Code | Meaning |
|---|---|
202 Accepted | Job queued. Returns the printJobId and PENDING status |
400 Bad Request | Malformed payload or unknown enumerated value |
409 Conflict | A job is already PENDING or IN_PROGRESS |
Job result
You have two mechanisms to learn the final result. Use one or both in parallel.
Option A: webhook
If you sent a webhookUrl when queueing, the terminal makes a POST to that URL when the job turns COMPLETED or FAILED.
Successful job
{"printJobId": "TICKET-190209","status": "COMPLETED","externalReference": "Table-14"}
Hardware failure
{"printJobId": "TICKET-190209","status": "FAILED","externalReference": "Table-14","errorCode": "OUT_OF_PAPER","errorMessage": "La impresora está sin papel."}
Option B: query the status
Use this when your system cannot receive inbound connections from the terminal, or as a webhook fallback.
Local Network
GET /terminal/v1/print_job?print_job_id=TICKET-190209
Cloud
POST /terminal/v1/{terminalSerial}/sync/print_job
{"print_job_id": "TICKET-190209"}
Poll every 2 or 3 seconds and stop once status is COMPLETED or FAILED.
{"printJobId": "TICKET-190209","status": "COMPLETED"}
Job statuses
status | Description |
|---|---|
PENDING | Queued, waiting its turn |
IN_PROGRESS | The driver is sending commands to the printer |
COMPLETED | Receipt printed and cut successfully |
FAILED | Physical error during printing. Check errorCode |
Hardware error codes
The possible values of errorCode are OUT_OF_PAPER, COVER_OPEN, COVER_INCOMPLETE, PAPER_JAM, PRINTER_HOT, MOTOR_HOT, CUTTER_ERROR, OFFLINE and UNKNOWN_ERROR. They arrive with type: TERMINAL-PRINTER in the error body.
A busy queue is not one of them: it arrives as TER-004 with type: TERMINAL, and over HTTP as a 409.
Best practices
- Send your own
printJobIdso you can reprint the same receipt and discard duplicates in your system. - Use
externalReferenceto tie the receipt to your order, table or invoice. - Answer the webhook with
2xxbefore processing it. A slow endpoint means you lose the notification. - Treat
FAILEDas an operational condition, not an integration error: show theerrorMessageto the operator so they can fix the physical problem. - Send images already binarized and at the right width. A heavy logo lengthens print time.
Accept payments with Kushki ONE
Process card-present payments and pair them with printing the receipt.
Printer errors
Look up the cause and the recommended action for every hardware error code.
Chile
Colombia
Ecuador
Mexico