For the complete documentation index, see llms.txt. This page is also available as Markdown.

ESC/POS Print API

A guide to setting up and using the ESC/POS Print API for the Vibrant Softpos App running on Sunmi devices with a built-in printer.

The following documentation is a work-in-progress and may be subject to change.

The Softpos app exposes two interfaces for triggering the Sunmi terminal's built-in printer:

  1. Android Broadcast Intent — For native Android apps running on the same device.

  2. Valgrind HTTP Endpoint — For web apps (PWAs), remote systems, or any client that cannot send Android intents.

Both accept raw ESC/POS commands and deliver them to the printer. The following sections describe each integration path.

Android Broadcast Intent

The broadcast intent API lets external POS applications trigger the Sunmi terminal's built-in printer directly, without having to foreground the Softpos app. This allows you to integrate custom receipt printing into your existing app flow without any disruptive app switching or deep linking. You can send raw ESC/POS commands as a byte array, and optionally receive a callback with the result of the print command.

1

Declare the permission

Add to your app's AndroidManifest.xml:

<uses-permission android:name="io.vibrant.softpos.permission.PRINT_ESC_COMMAND" />

The Softpos app declares this permission. Without it the broadcast is silently dropped by the OS.

2

Send the print intent

An ESC/POS print command is sent as a broadcast intent with the action io.vibrant.softpos.action.PRINT_ESC_COMMAND and the raw command bytes in the io.vibrant.softpos.extra.ESC_BYTES extra. For example:

fun printEscReceipt(
    context: Context,
    escBytes: ByteArray,
    correlationId: String? = null,          // optional — echoed back in the result callback
) {
    val intent = Intent("io.vibrant.softpos.action.PRINT_ESC_COMMAND").apply {
        component = ComponentName(
            "io.vibrant.softpos",
            "io.vibrant.softpos.print.EscPrintBroadcastReceiver"
        )
        putExtra("io.vibrant.softpos.extra.ESC_BYTES", escBytes)

        // Optional: request a result callback (see step 3)
        putExtra("io.vibrant.softpos.extra.CALLBACK_ACTION", "com.your.app.action.PRINT_RESULT")
        putExtra("io.vibrant.softpos.extra.CALLBACK_PACKAGE", "com.your.app")
        correlationId?.let { putExtra("io.vibrant.softpos.extra.CORRELATION_ID", it) }
    }
    context.sendBroadcast(intent)
}

The explicit ComponentName is required on Android 8+. Implicit broadcasts to exported receivers are blocked by the OS unless the component is named directly.

Note: Do not pass a permission string as the second argument to sendBroadcast. That parameter filters receivers by a permission they must hold — it does not assert the sender's permission. The sender permission is enforced automatically by the android:permission attribute on the receiver; you only need <uses-permission> in your manifest.

3

Receive the result (optional)

You can optionally receive a callback with the result of the print command by including the extras CALLBACK_ACTION and CALLBACK_PACKAGE in your intent. The Softpos app sends a broadcast with the specified action and package when the print command is processed, including extras for the result code, message, and correlation ID (if provided).

This allows you to handle success or failure of the print command and display appropriate feedback to the user. For example, you could show a confirmation message on success or an error dialog if the printer is unavailable.

To receive the callback, declare a BroadcastReceiver in your app with an intent filter matching the action you specified in CALLBACK_ACTION. For example:

<receiver android:name=".PrintResultReceiver" android:exported="false">
    <intent-filter>
        <action android:name="com.your.app.action.PRINT_RESULT" />
    </intent-filter>
</receiver>
class PrintResultReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val code = intent.getIntExtra("io.vibrant.softpos.extra.RESULT_CODE", -1)
        val message = intent.getStringExtra("io.vibrant.softpos.extra.RESULT_MESSAGE")
        val correlationId = intent.getStringExtra("io.vibrant.softpos.extra.CORRELATION_ID")

        when (code) {
            0 -> { /* success */ }
            1 -> { /* printer not available — hardware issue or non-Sunmi device */ }
            2 -> { /* feature not enabled for this merchant's plan */ }
            3 -> { /* invalid input — check message for details */ }
            4 -> { /* unexpected error — check message for details */ }
            5 -> { /* app not initialized — ask the merchant to open the Softpos app first */ }
        }
    }
}

Reference

Print request extras

Key
Type
Required
Description

ESC_BYTES

byte[]

Yes

Raw ESC/POS command buffer (no charset init needed — see section 5)

CHARSET

byte[]

No

Charset init bytes sent before the payload — defaults to UTF-8 (1C 43 FF), see section 5

CALLBACK_ACTION

String

No

Broadcast action for the result callback

CALLBACK_PACKAGE

String

No

Your package name (required if using callback)

CORRELATION_ID

String

No

Arbitrary ID echoed in the callback

Note: All extras should be prefixed with io.vibrant.softpos.extra.. The keys in the table above are shown without the prefix for readability.

Result codes

Code
Meaning

0

Print successful

1

Printer not available (hardware issue, broken printer, or non-Sunmi device)

2

Feature not enabled for this merchant's plan

3

Invalid input (e.g. empty or oversized byte array — see RESULT_MESSAGE)

4

Unexpected error — see RESULT_MESSAGE for details

5

App not initialized — the Softpos app must be opened and signed in before a print command can be processed

Intent targeting

Type
Value

Action

io.vibrant.softpos.action.PRINT_ESC_COMMAND

Target package

io.vibrant.softpos

Target class

io.vibrant.softpos.print.EscPrintBroadcastReceiver

Sender permission

io.vibrant.softpos.permission.PRINT_ESC_COMMAND

Character encoding

Charset initialisation is handled automatically — do not include any charset command in your payload. The Softpos app sends the charset init as a separate command immediately before your payload.

The default is UTF-8 (1C 43 FF — Sunmi proprietary command). To use a different charset, supply its raw initialisation bytes via the CHARSET extra:

Refer to the Sunmi ESC/POS documentation for the correct init bytes for your target charset. Any valid Sunmi charset command can be passed — there is no fixed list of supported values.

Do not start your payload with ESC @ (RESET — 0x1B 0x40). The printer reset command reverts the printer to power-on defaults, which undoes the charset init the Softpos app sent just before your payload. If you include a RESET in your payload, the charset will revert to the printer's default and your text may print blank or garbled. Begin your payload directly with your formatting commands (alignment, font size, etc.).

Minimal test

A simple payload to verify the integration end-to-end before building your full receipt:

A successful result prints "Integration test" centred with a partial cut, and your callback receiver gets result code 0.

Constraints

  • Max payload: 512 KB. Payloads above this are rejected with result code 3.

  • Timing: Send after your payment confirmation is fully processed. The Softpos app will be in the background at that point and will handle the broadcast without any app switch.

  • Softpos must be installed: If the app is not installed the broadcast is a no-op.

  • Merchant must have printing enabled: Accounts without the printing feature return code 2.

Valgrind HTTP Endpoint

For clients that cannot send Android broadcast intents, the Vibrant backend provides an HTTP endpoint that delivers the print command to the target device via a push message.

The endpoint accepts the same ESC/POS byte payload, just like the broadcast intent API, but Base64-encoded in a JSON body. It also accepts an optional correlationId for tracing the print command through device logs.

Important to note that it is the Valgrind API and not the POS API. The purpose and use is the same with the same API key, but the API URLs differ.

Request body schema:

Field
Type
Required
Description

printBytes

String

Yes

The raw ESC/POS command buffer, Base64-encoded

correlationId

String

No

Opaque tracing ID echoed in device-side logs for end-to-end traceability

Prerequisites

  • The Softpos app must be installed and signed in on the target device.

  • The merchant must have the receipt printing feature enabled.

  • The device must support printing (Sunmi hardware with a built-in printer).

The same charset rules apply as with the broadcast intent API (see section A.5). The ESC/POS bytes you Base64-encode should already include your formatting commands. Do not include ESC @ (reset) at the start of your payload.

Minimal test

Base64-encode a simple ESC/POS payload and send it via the endpoint:

Last updated