SplitEasy
Receipt-attached bill-splitting integration. Diners scan a QR on the printed receipt to split the meal with friends.
Integration shape
| Slug | spliteasy |
| Category | receipt |
| Capabilities | receipt_qr, payment_webhook_inbound |
| Mode | add (stacks with other receipt_qr installs) |
| Auth | platform_shared · HS256, 32-byte secret |
| Intake host | api.spliteasy.uncle-z.com |
| Diner landing | s.uncle-z.com/b/<token> |
| Data residency | id (servers in Indonesia) |
End-to-end flow
- Owner installs SplitEasy from
posz.uncle-z.com/integrations. One-click. No SplitEasy account needed. Posz makes no provisioning call to SplitEasy at install time. - Cashier closes an order. Posz’s receipt builder calls the SplitEasy adapter for capability
receipt_qr. - Adapter signs a 15-min JWT carrying the merchant identity and POSTs
/v1/external/billswith the bill payload (3-second timeout). SplitEasy auto-creates the merchant record on the first bill it sees for thatmerchant.idand returns abill_token+share_url. - Posz embeds the QR encoding
share_url(orhttps://s.uncle-z.com/b/<bill_token>ifshare_urlis empty) in the receipt footer. If the intake call fails or times out, the receipt prints without the QR. - Diner scans → SplitEasy landing → split with friends → settle inter-friend.
- SplitEasy webhooks Posz at
/v1/integrations/spliteasy/webhookwithbill.settled/bill.expired/bill.cancelled. Posz verifies the signature and records the event (deduped per event +bill_token).
Auth JWT
HS256 signed with the platform-shared 32-byte secret. 15-minute TTL — sign a fresh JWT per request; never cache.
{
"iss": "posz",
"aud": "spliteasy",
"exp": 1714493700,
"iat": 1714492800,
"merchant": {
"id": "<posz_merchant_uuid>",
"name": "Maple & Oat",
"location": "Senopati",
"city": "Jakarta",
"email": "owner@mapleandoat.id",
"currency": "IDR",
"locale": "id",
"timezone": "Asia/Jakarta",
"logo_url": "https://posz-uploads.s3.ap-southeast-3.amazonaws.com/..."
}
} email, phone and logo_url are omitted when the merchant has none (never sent as null). currency, locale and timezone default to IDR, id and Asia/Jakarta.
Why platform-shared (not OAuth)
SplitEasy is consumer-facing — diners use the app, not merchants. There’s nothing for the merchant to log into and authorize. Posz vouches for whichever merchant.id it embeds; SplitEasy auto-creates the merchant record on first encounter.
Bill intake
Amounts are ISO 4217 integer minor units, even though
the fields are named _cents. For zero-decimal currencies
(IDR, JPY, KRW, VND…) the minor unit equals the major unit, so
49000 means Rp 49.000 — not Rp 490.
(Posz stores rupiah × 100 internally and divides by 100
before sending.)
POST /v1/external/bills HTTP/1.1
Host: api.spliteasy.uncle-z.com
Authorization: Bearer <jwt>
Content-Type: application/json {
"merchant_ref": "posz_order_<order_uuid>",
"currency": "IDR",
"items": [
{
"name": "Nasi Goreng",
"qty": 1,
"unit_price_cents": 49000,
"modifiers": [{ "name": "Extra telur", "price_cents": 5000 }]
}
],
"discounts": [
{ "name": "Discount", "amount_cents": 5400, "scope": "order" }
],
"service": { "type": "percent", "value": 5.5 },
"tax": { "type": "percent", "value": 11 },
"tip_amount_cents": 0,
"loyalty_discount_amount_cents": 0,
"metadata": {
"table_id": "B1",
"server": "Agus",
"posz_order_id": "<uuid>"
}
} {
"bill_token": "splt_aB3kP9zL2qR4",
"share_url": "https://s.uncle-z.com/b/splt_aB3kP9zL2qR4",
"expires_at": "2026-06-01T08:30:00Z"
} Idempotency on retry
Dedup key is (jwt.iss, merchant_ref). Posz’s merchant_ref is posz_order_<uuid> — globally unique per order. Posz can safely retry (e.g. transient network failure) and SplitEasy returns the same bill_token.
Settlement webhook
SplitEasy posts events back to Posz at
/v1/integrations/spliteasy/webhook. Signed with
the same shared secret: lowercase hex HMAC-SHA256 of the raw body
(an optional sha256= prefix is accepted) in
X-SplitEasy-Signature. Posz also accepts the digest in
X-Posz-Signature or X-Webhook-Signature.
Three event types; the body must carry event,
bill_token and merchant_ref.
| Event | When | Posz behavior |
|---|---|---|
bill.settled | Group has finished paying | Recorded as an inbound event; install’s last-event time updated |
bill.expired | Token TTL hit, never settled | Recorded as an inbound event |
bill.cancelled | Paymaster aborted (closed app, restaurant voided bill) | Recorded as an inbound event |
Responses: 200 {"ok": true, "event_type": "…"};
a replay of an already-processed event + bill_token
returns 200 {"deduped": true}.
401 invalid_signature on a bad or missing signature,
400 bad_request when merchant_ref isn’t
posz_order_<uuid> or the order is unknown, and
404 not_found when the merchant no longer has SplitEasy installed.
POST https://api.posz.uncle-z.com/v1/integrations/spliteasy/webhook
X-SplitEasy-Signature: <hex>
Content-Type: application/json {
"event": "bill.settled",
"merchant_ref": "posz_order_<uuid>",
"bill_token": "splt_aB3kP9zL2qR4",
"settled_at": "2026-04-30T14:23:11+07:00",
"group_size": 4,
"settlement_methods": ["bifast", "cash", "qris"]
} Non-blocking by design
The settlement webhook is informational — Posz already got paid by the paymaster at the cashier. If SplitEasy can’t reach Posz, nothing breaks operationally; nothing on the Posz side waits for these events.
Uninstall semantics
Owner clicks Uninstall in /integrations. Posz deletes
the install on its side only — it makes no call
to SplitEasy (there is no disconnect endpoint).
- Posz stops calling
/v1/external/billsfor that merchant, so new receipts print without the SplitEasy QR. - Receipts already printed keep pointing at SplitEasy; whether a
bill_tokenstill opens is up to SplitEasy’s own token TTL. - Webhooks for that merchant’s bills now get
404 not_found(no active install). - Re-installing creates a fresh install. The JWT still carries the same Posz
merchant.id, so SplitEasy can keep using its existing merchant record.