WORKED EXAMPLE · LIVE

SplitEasy

Receipt-attached bill-splitting integration. Diners scan a QR on the printed receipt to split the meal with friends.

Integration shape

Slugspliteasy
Categoryreceipt
Capabilitiesreceipt_qr, payment_webhook_inbound
Modeadd (stacks with other receipt_qr installs)
Authplatform_shared · HS256, 32-byte secret
Intake hostapi.spliteasy.uncle-z.com
Diner landings.uncle-z.com/b/<token>
Data residencyid (servers in Indonesia)

End-to-end flow

  1. Owner installs SplitEasy from posz.uncle-z.com/integrations. One-click. No SplitEasy account needed.
  2. Posz POSTs /v1/external/merchants to SplitEasy with the merchant payload — SplitEasy creates a merchant record and returns external_merchant_id.
  3. Cashier closes an order. Posz’s receipt builder calls the SplitEasy adapter for capability receipt_qr.
  4. Adapter signs a 15-min JWT and POSTs /v1/external/bills with the bill payload — SplitEasy returns a bill_token.
  5. Posz embeds the QR encoding https://s.uncle-z.com/b/<bill_token> in the receipt footer.
  6. Diner scans → SplitEasy landing → split with friends → settle inter-friend.
  7. SplitEasy webhooks Posz at /v1/integrations/spliteasy/webhook with bill.settled / bill.expired / bill.cancelled. Posz writes an audit log row.

Auth JWT

HS256 signed with the platform-shared 32-byte secret. 15-minute TTL — sign a fresh JWT per request; never cache.

Authorization: Bearer <jwt> json
{
  "iss": "posz",
  "aud": "spliteasy",
  "exp": 1714493700,
  "iat": 1714492800,
  "merchant": {
    "id":         "<posz_merchant_uuid>",
    "name":       "Maple & Oat",
    "location":   "Senopati",
    "city":       "Jakarta",
    "email":      "owner@mapleandoat.id",
    "phone":      null,
    "currency":   "IDR",
    "locale":     "id",
    "timezone":   "Asia/Jakarta",
    "logo_url":   "https://posz-uploads.s3.ap-southeast-3.amazonaws.com/..."
  }
}

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

Currency in minor units (_cents) end-to-end. ISO 4217 currency code separate. For zero-decimal currencies (IDR, JPY, KRW, VND…) the minor unit equals the major unit, so 49000 means Rp 49.000.

POST /v1/external/bills http
POST /v1/external/bills HTTP/1.1
Host: api.spliteasy.uncle-z.com
Authorization: Bearer <jwt>
Content-Type: application/json
Request body json
{
  "merchant_ref": "posz_order_<order_uuid>",
  "currency":     "IDR",
  "items": [
    {
      "name": "Nasi Goreng",
      "qty": 1,
      "unit_price_cents": 4900000,
      "modifiers": [{ "name": "Extra telur", "price_cents": 500000 }]
    }
  ],
  "discounts": [
    { "name": "Member 10%", "amount_cents": 1200000, "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>"
  }
}
Response · 201 json
{
  "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 using HMAC-SHA256 in X-SplitEasy-Signature. Three event types.

EventWhenPosz behavior
bill.settledGroup has finished payingAudit log row + usage stats
bill.expired30-day token TTL hit, never settledCleans up audit placeholder
bill.cancelledPaymaster aborted (closed app, restaurant voided bill)Cleans up audit placeholder
Webhook delivery http
POST https://api.posz.uncle-z.com/v1/integrations/spliteasy/webhook
X-SplitEasy-Signature: <hex>
Content-Type: application/json
Body · bill.settled 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. Posz will retain the unsettled placeholder until a future bill.expired arrives.

Disconnect semantics

Owner clicks Uninstall in /integrations. Posz fires POST /v1/external/merchants/{id}:disconnect on SplitEasy. SplitEasy flips merchant status to disconnected.

  • Subsequent intake calls return 410 Gone (not 401 — the JWT is still valid; it’s the merchant relationship that’s severed).
  • Existing printed receipts stay scannable until 30-day bill_token TTL — never strand a customer mid-meal.
  • A subsequent re-Connect re-activates the same record (no duplicate; analytics continuity preserved).