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 request and response structure is identical across topologies. The only thing that changes is the base URL.
| Topology | Base URL |
|---|---|
| Local Network (LAN / Wi-Fi) | http://{TERMINAL_IP}:6868/terminal/v1 |
| Cloud (Internet) — UAT | https://uat-cloudt.kushkipagos.com/terminal/v1/{terminalSerial}/sync |
| Cloud (Internet) — Production | https://cloudt.kushkipagos.com/terminal/v1/{terminalSerial}/sync |
Authentication
Include these headers with every request, the same as in the Payment API:
| Header | Value |
|---|---|
Authorization | HMAC-SHA256 signature of the request body, Base64-encoded, using your Business-Code as the key |
timestamp | Unix timestamp in milliseconds |
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, BUSY, PRINTER_HOT, MOTOR_HOT, CUTTER_ERROR, OFFLINE and UNKNOWN_ERROR.
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