Developers Guides Receipt Printing

Receipt Printing

Two ways to print a receipt on a terminal with a built-in printer or on the FinPrinter: let Surfboard render it from a template, or build the layout yourself and send ESC/POS. How to choose, and how to set up each.

In-StoreAPIReceiptsPrintingTemplatesESC/POSFinPrinter

Add this to your codebase

Paste it into Claude Code, Codex, Cursor or any coding agent. It points the agent at this guide in machine-readable form, so it writes against the real API instead of a guess. Wire up the MCP server once and it can read the rest of the platform too.

Overview

Surfboard prints on two kinds of device: terminals with a built-in printer, such as SurfPrint Pro, and the FinPrinter, the standalone cloud printer for a counter, a pickup point or a kitchen. Whichever device you print on, there are two ways to get a receipt onto paper, and you pick whichever fits your POS better.

Printing templatesESC/POS
Who renders the receiptSurfboardYou
What you sendAn ID: paymentId, transactionId or orderIdThe complete ESC/POS byte stream
Layout controlAdd or remove pre-defined receipt elementsFull control, line by line
Where the layout livesPartner Portal, at partner, merchant or store levelYour code
RequirementorderLines on the orderUTF-8 text inside the ESC/POS contract
FontsHandled by the templateOne font, with bold and size variations
MaintenanceSurfboardYou
Prints more than receiptsNoYes: kitchen slips, bongs, pickup tickets

If you already send orderLines on every order and want a receipt that looks right without owning its layout, use templates. If you need a layout Surfboard does not offer, or you want to print things that are not receipts at all, use ESC/POS. The two are not exclusive: many integrations print the customer receipt from a template and a kitchen slip from ESC/POS on the same device.

Note: This guide is about paper. For email receipts, hosted receipt links and the fiscal receipt fields, see the Receipts guide.

Prerequisites

  • A Surfboard developer account with valid API credentials (API-KEY and API-SECRET)
  • A terminal with a built-in printer or a FinPrinter, registered to a merchant and store, and its terminalId
  • For templates: orders created with orderLines, and access to the Partner Portal

The printers

Both kinds of device are registered the same way and get a terminalId, so the print endpoints do not care which one they are talking to. The device must be online for a job to print; a SUCCESS response means the job was dispatched, not that paper came out.

DevicePrinterPaperNormal-text columnsTypical placement
Terminals with a built-in printerBuilt in58 mm32Handheld: prints at the table, on the floor or at the counter
FinPrinterStandalone, cloud-connected80 mm48Counter, pickup point or kitchen, with no terminal beside it

Terminals with a built-in printer print where the payment happens. The terminal that took the payment is the default target of the print call, so a receipt on the device the customer just tapped needs no terminalId at all. The current line-up is on the products page, filtered to devices with a printer.

FinPrinter has no screen and takes no payments. It connects over Wi-Fi or Ethernet, and you register it with the same device registration call as a terminal, using the serial number on the back of the unit as the registration identifier. See Device Registration. Once registered it shows up as a device next to the merchant’s terminals in the portals and the API, and both print endpoints in this guide accept its terminalId. Because every job routes through Surfboard’s cloud, your POS needs no driver and no cable to the printer.

The column counts matter only on the ESC/POS path, where you format every line yourself. Templates adapt to the paper width on their own.

Path 1: Printing templates

On this path you never render a receipt. You describe what the receipt should contain by choosing from Surfboard’s pre-defined receipt fields, Surfboard lays it out, and at print time you send an ID. Surfboard maps that ID to the order, fills in the template and prints.

Set up the template

Templates are configured in the Partner Portal. A template is a list of the receipt elements you want, built from the pre-defined fields: merchant details, the order lines with quantities and prices, VAT breakdown, the payment details from the card scheme, footer text and so on. Add and remove elements to fit what you and the merchant need.

A template can be set at three levels:

LevelUse it for
PartnerThe default for every merchant you board
MerchantA merchant whose receipt should differ from your default
StoreOne location that needs its own variant, for example a different footer or fiscal line

The most specific level wins, so the usual setup is one partner-level default and overrides only where a merchant or store asks for one. If you would rather set the merchant default from code, the merchant configuration endpoint takes a terminalReceiptTemplateId:

PATCH /merchants/{merchantId}/configuration
{
  "terminalReceiptTemplateId": "<template id from the Partner Portal>"
}

Send order lines

The one requirement on this path is that the receipt data comes from the order, so orderLines must be present on the create order call. An order created without lines has nothing for the template to render. If your POS already sends lines, which it does if the terminal shows a basket, there is no extra work beyond setting up the template.

The fiscal fields that Nordic regulators expect on a receipt, such as the register name and control unit serial, are stored against the order separately with PUT /receipts/{orderId}. That call is covered in Receipts, and the template picks the fields up when they are present.

Print

POST /receipts/{id}/print

The {id} is a paymentId, transactionId or orderId, whichever your POS has to hand. Surfboard resolves it to the order and its receipt data.

Request body:

{
  "terminalId": "trm_abc123",
  "templateType": "TRANSACTION_RECEIPT"
}
ParameterTypeRequiredDescription
terminalIdstringNoThe device to print on. Defaults to the terminal that processed the transaction. Set it to send the receipt to a FinPrinter at the counter, or to a different terminal than the one that took the payment.
templateTypestringNoTRANSACTION_RECEIPT (default) prints the customer receipt. SALES_SUMMARY prints a summary instead.

Response:

{
  "status": "SUCCESS",
  "message": "Receipt sent to the printer successfully."
}

Which template renders is decided by the configuration in the Partner Portal, resolved from the store up through the merchant to the partner. Nothing in the print call names a template, which is what lets you change a merchant’s receipt without touching the POS.

Note: The older PUT /receipts/{id}/print form, which took a templateId and a language in the body, still works but is superseded by the POST above. New integrations should use POST.

For a register built on Surfboard, the order itself can carry autoPrintReceipt under controlFunctions.receipt, so the receipt prints when the payment completes with no print call at all. See Build Your Own ECR.

Path 2: ESC/POS

On this path you build the whole receipt: every line, every column, every bold header. You send the finished ESC/POS byte stream to a specific printer, and Surfboard validates it and passes it through. This is the path when you already have a receipt renderer in your POS, when the templates cannot express the layout you need, or when the thing you are printing is not a receipt.

PUT /receipts/{terminalId}/escpos

Note that the path takes a terminalId, not a payment or order ID. Nothing ties the job to an order, which is what makes it usable for kitchen slips and pickup tickets.

Request body:

{
  "escposCommands": "<base64 ESC/POS>",
  "codePages": "UTF-8"
}

Three things to know before you commit to this path:

  • One font. The printers support a single typeface, with bold, a condensed variant and two larger sizes. If a design calls for anything else, it has to be sent as an image, and images are capped at 384 dots wide.
  • The contract is enforced. With "codePages": "UTF-8" set, the stream is validated before it reaches hardware. Text must be UTF-8, only a fixed set of commands is accepted, and an invalid stream is rejected whole with PR_0006 rather than printed halfway. The full rules, a worked receipt and a preflight validator are in ESC/POS Printing.
  • Formatting and maintenance sit with you. Line widths differ by device and by text size, 32 columns on a 58 mm terminal against 48 on the FinPrinter, so a layout built for one printer needs checking on the other. When a merchant wants a different footer, that is a change in your code, not in a portal.

The trade is the same one a template makes in the other direction: you own the layout, so you own the work of keeping it right.

Choosing, in practice

  • A POS that already sends orderLines and has no receipt renderer. Templates. Set one at partner level and you are done.
  • A POS with its own receipt engine, migrating to Surfboard. ESC/POS, which lets you keep the receipts your merchants already know. Read the contract first; generic ESC/POS libraries often emit raster images and charset commands it rejects.
  • Customer receipt plus kitchen slip. Both. Templates for the receipt on the terminal that took the payment, ESC/POS for the bong on a FinPrinter in the kitchen.
  • A receipt that must carry something the template fields do not have. Check the available elements in the Partner Portal first; if the field is not there, ESC/POS.

Troubleshooting

SymptomLikely causeFix
SUCCESS but nothing printedDevice offline, or busy with another commandCheck the device in the Merchant Portal and retry
Template receipt is empty or missing linesOrder was created without orderLinesSend lines on the create order call; they cannot be added after the fact
Receipt prints on the wrong deviceterminalId omitted, so it defaulted to the terminal that took the paymentPass the terminalId of the printer you want
Template changes do not showA more specific level overrides yoursCheck for a merchant or store template overriding the partner default
PR_0006 on the ESC/POS pathA command outside the contract, or non-UTF-8 textThe message names the byte offset. See the ESC/POS Printing error table

API Quick Reference

OperationMethodEndpoint
Print from a templatePOST/receipts/{id}/print
Print a custom ESC/POS jobPUT/receipts/{terminalId}/escpos
Set a merchant’s default templatePATCH/merchants/{merchantId}/configuration
Register a FinPrinterPOST/merchants/{merchantId}/stores/{storeId}/devices

For the full endpoint reference, see the Receipts API documentation. For the ESC/POS contract itself, see ESC/POS Printing; for email, links and fiscal receipt data, see Receipts.

Ready to get started?

Create a sandbox account and start building your integration today.