Skip to Content

Subscribe to Terminal Events

In cloud integration mode, after you place an order via Nexus API, the terminal goes through a series of steps — card presentation, PIN entry, authorization, signature capture, receipt printing — before returning to idle. Terminal Events push these intermediate states to you in real time, so you can:

  • Drive your UI with live progress (e.g., show “Waiting for card”, “Processing”, “Printing receipt”)
  • Know exactly when the terminal is idle and ready for the next transaction
  • Avoid sending a new order while the terminal is still busy (which would be rejected)

Terminal Events vs. Transaction Result Webhook: The Transaction Result Webhook tells you the payment outcome (success / failure). Terminal Events tell you what the terminal is doing throughout the process. They serve different purposes and are complementary.

Why You Need This

After your server receives the transaction result webhook, the terminal may still be capturing a signature or printing a receipt. If you immediately dispatch another order, the terminal will reject it because it can only process one transaction at a time.

Terminal Events solve this by giving you a TRANSACTION_ENDED event that signals the terminal has completed all post-authorization steps and is back to idle — the safe moment to send the next order.

Interactive Demo

The animation below shows a POS application placing a transaction via the SUNBAY cloud, the events triggered during the transaction being pushed back through webhooks, and how the POS UI updates in real time. Click “Play” to try it:

POS App
Order·#ORDER-1001
$10.00
Status
Ready to submit
SUNBAY Cloud
API call
Dispatch to terminal
Event webhook
Nexus API · Webhook
Payment Terminal
Cardholder screen
Ready

Event Lifecycle

Every transaction produces events across four stages:

Stage Overview

StageEventRequiredDescription
Pre-PaymentORDER_RECEIVEDYesTerminal accepted the order. If the terminal rejects (busy / invalid params / not signed in), it skips directly to TRANSACTION_ENDED.
PaymentPAYMENT_PRESENTEDNoPayment medium presented: card swipe / insert / tap, or QR code scan.
PIN_ENTERINGNoCardholder is entering PIN (only when PIN is required).
PAYMENT_PROCESSINGNoTransaction is being processed (authorization in progress).
Post-PaymentSIGNINGNoCapturing signature (skipped for no-signature transactions).
PRINTINGNoReceipt is being printed or electronic receipt is being generated.
PRINT_COMPLETEDNoPrinting finished (success or failure).
EndTRANSACTION_ENDEDYesTerminal has completed all steps and returned to idle. Safe to dispatch next order.

TRANSACTION_ENDED does not indicate transaction success or failure. It only means the terminal is idle. Use the Transaction Result Webhook or Transaction Query API to determine the payment outcome.

Handling Tips

  • Required events guarantee a stable state-machine skeleton. Your integration can rely on receiving at least: ORDER_RECEIVEDTRANSACTION_ENDED.
  • Optional events fire only when the corresponding step actually happens. Drive your UI by event type — do not assume a fixed sequence of all events.
  • Abnormal exits: Any stage can terminate early. The flow always ends with TRANSACTION_ENDED regardless of where the interruption occurred.

Event Payload

Field Reference

NameTypeRequiredDescription
topic
stringYes
Fixed value: SEMI_INT_CLOUD_TERMINAL_EVENT
messageId
stringYes
Globally unique ID for idempotent deduplication
terminalSn
stringYes
Terminal serial number
timestamp
stringYes
RFC 3339 timestamp when the event was generated on the terminal
payload
objectYes
Event business data

Examples

PAYMENT_PRESENTED (card tap):

{ "topic": "SEMI_INT_CLOUD_TERMINAL_EVENT", "messageId": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6", "terminalSn": "P200-SN12345", "timestamp": "2026-07-08T10:15:30.123Z", "payload": { "eventStage": "PAYMENT", "eventType": "PAYMENT_PRESENTED", "appId": "app_001", "merchantId": "M0001", "transactionRequestId": "TR20260708001", "transactionId": "TXN20260708001", "paymentMethod": { "category": "CARD", "entryMode": "CONTACTLESS" } } }

PRINT_COMPLETED (print failed):

{ "topic": "SEMI_INT_CLOUD_TERMINAL_EVENT", "messageId": "c5f9d4e3af6a6b8c1e3d7fa4c9b5d2e", "terminalSn": "P200-SN12345", "timestamp": "2026-07-08T10:15:45.789Z", "payload": { "eventStage": "POST_PAYMENT", "eventType": "PRINT_COMPLETED", "appId": "app_001", "merchantId": "M0001", "transactionRequestId": "TR20260708001", "transactionId": "TXN20260708001", "receipt": { "printOption": "MERCHANT", "printResult": "FAILED", "printFailReason": "Out of paper" } } }

TRANSACTION_ENDED:

{ "topic": "SEMI_INT_CLOUD_TERMINAL_EVENT", "messageId": "d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2", "terminalSn": "P200-SN12345", "timestamp": "2026-07-08T10:15:50.456Z", "payload": { "eventStage": "END", "eventType": "TRANSACTION_ENDED", "appId": "app_001", "merchantId": "M0001", "transactionRequestId": "TR20260708001", "transactionId": "TXN20260708001" } }

How to Subscribe

Subscription is per-transaction: include a terminalEventNotifyUrl parameter when placing an order, and SUNBAY will push all terminal events for that transaction to that URL. Different transactions can subscribe to different URLs, or opt out entirely by omitting the parameter.

Supported APIs

The following transaction APIs accept terminalEventNotifyUrl:

APIPathAlways produces terminal events?
Sale/v1/semi-integration/transaction/saleYes
Pre-authorization/v1/semi-integration/transaction/authYes
Forced Authorization/v1/semi-integration/transaction/forced-authYes
Incremental Authorization/v1/semi-integration/transaction/incremental-authOnly when pushToTerminal=true
Pre-Authorization Completion/v1/semi-integration/transaction/post-authOnly when pushToTerminal=true
Refund/v1/semi-integration/transaction/refundOnly when pushToTerminal=true
Void/v1/semi-integration/transaction/voidOnly when pushToTerminal=true

About pushToTerminal: Incremental Authorization, Pre-Authorization Completion, Refund, and Void can be executed either purely on the server or dispatched to the terminal (e.g., a refund that requires the cardholder to re-present the card). The transaction only involves the terminal — and therefore only produces terminal events — when pushToTerminal=true. When pushToTerminal=false, the transaction is server-only and no terminal events will be pushed even if terminalEventNotifyUrl is set.

Request Parameter

Add the following field to your transaction request body:

FieldTypeRequiredDescription
terminalEventNotifyUrlstringNoHTTPS URL to receive terminal events. If omitted, no terminal events are pushed.

Example:

{ "transactionRequestId": "TR20260708001", "referenceOrderId": "ORDER_10001", "priceCurrency": "USD", "orderAmount": 1000, "notifyUrl": "https://your-server.com/webhooks/transaction-result", "terminalEventNotifyUrl": "https://your-server.com/webhooks/terminal-events" }

notifyUrl and terminalEventNotifyUrl are independent parameters. Configure them to point to two separate endpoints — the payloads have different structures (transaction result vs. terminal event envelope), and keeping the endpoints separate keeps your routing and handling logic clean and decoupled.

Delivery

Events are delivered via HTTP POST with Content-Type: application/json, using the exact same headers and security model as the Transaction Result Webhook:

  • HMAC-SHA256 signature via X-Signature header
  • Same Webhook Secret as the transaction result webhook
  • X-Client-Request-Id / X-Timestamp headers for tracing and replay protection

Your endpoint must return HTTP 200 to acknowledge receipt. Signature verification code from your transaction result webhook can be reused as-is.

Reliability

Ordering

Events for the same transactionId are ordered by timestamp. If you receive an event with a timestamp earlier than the latest event you already processed for that transaction, treat it as stale and discard it.

Idempotency

Use messageId to deduplicate. You may receive the same event more than once due to network retries.

Retry

Terminal events are time-sensitive — delivering them late defeats the purpose. We therefore use short, fast retries only. If your endpoint returns a non-HTTP 200 response or times out:

AttemptInterval
11 second
25 seconds
330 seconds

Events that still fail after three attempts are dropped and not replayed. This is intentionally different from the transaction result webhook’s long retry policy: terminal events are a live state stream, and stale events are of no use to the developer.

Circuit Breaking

If your endpoint fails consecutively past our threshold, we suspend delivery for 10 minutes to protect both systems. Delivery resumes automatically after the cooldown. Events generated during the suspension are not replayed; only events for new transactions are pushed after recovery.

Best Practices

  1. Gate your next order on TRANSACTION_ENDED: Do not dispatch a new transaction to the same terminal until you receive this event. The terminal will reject concurrent orders.

  2. Use events for UI, not for business logic: Terminal Events tell you process state, not payment outcome. Always rely on the Transaction Result Webhook or Query API for final payment status.

  3. Handle missing optional events gracefully: Not every transaction will produce PIN_ENTERING, SIGNING, or PRINTING. Design your UI to skip smoothly to whatever event arrives next.

  4. Deduplicate by messageId: Network conditions may cause duplicate delivery. Use messageId as an idempotency key (e.g., store in Redis with a 24-hour TTL).

  5. Implement a timeout fallback: If you do not receive TRANSACTION_ENDED within a reasonable window (e.g., 5 minutes), query the terminal status via API rather than waiting indefinitely.

  6. Subscribe only when you need it: terminalEventNotifyUrl is optional. For transactions where live process feedback isn’t needed (e.g., back-office server-initiated flows), omit the parameter to avoid unnecessary webhook traffic.

Last updated on