POS INTEGRATION
Your POS. Your orders.
Payments through SatsRail.
Add Lightning payments to your existing Android, desktop, or web POS.
Send an amount, display an invoice, and confirm payment.
Keep your existing checkout
Your POS owns the cart, taxes, inventory, customer details, and receipts. SatsRail requests an invoice from the merchant's wallet provider and verifies settlement. The customer pays the merchant directly.
Your POS
Calculate the total, associate the invoice with your sale, show the QR code, and issue the receipt after confirmation.
SatsRail
Request the Lightning invoice, monitor settlement, and return payment status. Funds go directly to the merchant's wallet.
No catalog import or basket data is required. SatsRail creates a minimal internal order with a generic “Payment” line item to support the payment record. Your POS remains the source of truth for the sale.
Before you start
Activate your merchant account, connect a compatible Lightning Address with payment verification, and configure your merchant currency. Create a secret API key with permission to create payment requests and read invoice status.
For an Android app, keep the sk_live_... key on your backend. Authenticate the terminal to your backend and let the backend call SatsRail. Publishable pk_... keys cannot call the payment endpoints below. Do not bundle a merchant secret key into the APK.
Recommended connection
Android POS → your backend → SatsRail → merchant wallet provider
Invoice and status responses travel back to the POS through your backend. The customer pays the merchant's invoice from their own Lightning wallet.
1. Request a payment
Calculate the final total in your POS, including any tax or tip. Make this request from your backend:
curl --request POST 'https://satsrail.com/api/v1/m/payment_requests' \
--header "Authorization: Bearer $SATSRAIL_SECRET_KEY" \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: pos-payment-7f982e41' \
--data '{
"payment_request": {
"amount_cents": 2500,
"payment_method": "lightning"
}
}'
amount_cents is in the merchant's configured currency: for a USD merchant, 2500 means $25.00. This endpoint does not accept a per-request currency. SatsRail calculates the invoice amount in sats.
A successful request returns HTTP 201. Example response (the invoice string is abbreviated):
{
"object": "payment_request",
"id": "4e14d219-5321-4b64-a1d3-830d11d60945",
"payment_method": "lightning",
"amount_sats": 25000,
"payment_request": "lnbc...",
"payment_hash": "...",
"created_at": "2026-09-22T12:00:00Z",
"expires_at": "2026-09-22T12:15:00Z"
}
The returned id is the invoice ID. Persist its association with your local sale before showing the QR code:
your_sale_id → satsrail_invoice_id
Also save the invoice string, expiry, and the idempotency key for that payment attempt. Keep your sale reference in your own database; you do not need to send it to SatsRail.
2. Display the invoice
Return the payment details to Android and encode the exact payment_request string as a QR code. Show the amount and a countdown based on expires_at. The customer scans the QR with their Lightning wallet.
Use the invoice returned for this sale, rather than a static Lightning Address QR. Each invoice gives your POS a specific payment to track. Displaying or scanning the QR does not mean the sale has been paid.
3. Confirm payment
While the payment screen is open, have your backend check the invoice status. Start with a five-second interval, adjust to your API limits, and share one check across clients watching the same invoice.
curl 'https://satsrail.com/api/v1/m/invoices/4e14d219-5321-4b64-a1d3-830d11d60945/status' \
--header "Authorization: Bearer $SATSRAIL_SECRET_KEY"
This endpoint checks settlement server-side. Relevant fields from a paid response:
{
"status": "paid",
"paid": true,
"expired": false,
"paid_at": "2026-09-22T12:00:18Z",
"expires_at": "2026-09-22T12:15:00Z",
"payment_method": "lightning"
}
Only when paid is true, mark your local sale paid and issue the receipt. Make that update idempotent: repeated status responses must not fulfill the sale twice. A paid response also includes payment details beyond the fields shown here.
Handle interruptions
- App restart or lost connection
- Load the saved invoice ID and check its status again. SatsRail monitors payment independently of the terminal. A network error means the status is unknown; it does not mean the customer failed to pay.
- Invoice expiry
- Remove the expired QR and check status before offering a new payment attempt. Late confirmation can still arrive. Retain earlier invoice IDs and reconcile them against the sale so a late payment is not lost or fulfilled twice.
- Retrying invoice creation
- Generate a unique
Idempotency-Keyfor each payment attempt and reuse it with the same payload on retries. Successful responses are cached for 24 hours. Serialize creation per sale in your backend: this cache does not guarantee deduplication of concurrent requests, failed requests, or retries after cache loss. An ambiguous timeout needs reconciliation before creating another payment attempt. - API errors
- Check
error.codeanderror.messageon non-success responses. Correct authentication or permission failures (401/403), inspect validation or wallet errors (422), and back off on429, honoringRetry-Afterwhen supplied. Do not show a payment QR until you have a successful invoice response.
Optional: receive payment notifications
Your backend can subscribe to payment.received webhooks and relay updates to Android. Verify webhook signatures and deduplicate events before updating a sale. Keep invoice-status checks for reconnects and recovery.
SatsRail also offers OrderStatusChannel over ActionCable. It requires SatsRail's internal order ID, which the payment-request creation response does not expose. The invoice-status endpoint above is the simplest starting point for this integration.
Build your payment screen
Exercise pending payment, confirmed payment, expiry, repeated responses, and reconnecting to an existing invoice before using the integration at the counter.
API reference Account setup