WORKED EXAMPLE

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. Posz makes no provisioning call to SplitEasy at install time.
  2. Cashier closes an order. Posz’s receipt builder calls the SplitEasy adapter for capability receipt_qr.
  3. Adapter signs a 15-min JWT carrying the merchant identity and POSTs /v1/external/bills with the bill payload (3-second timeout). SplitEasy auto-creates the merchant record on the first bill it sees for that merchant.id and returns a bill_token + share_url.
  4. Posz embeds the QR encoding share_url (or https://s.uncle-z.com/b/<bill_token> if share_url is empty) in the receipt footer. If the intake call fails or times out, the receipt prints without the QR.
  5. Diner scans → SplitEasy landing → split with friends → settle inter-friend.
  6. SplitEasy webhooks Posz at /v1/integrations/spliteasy/webhook with bill.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.

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",
    "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
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": 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>"
  }
}
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: 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.

EventWhenPosz behavior
bill.settledGroup has finished payingRecorded as an inbound event; install’s last-event time updated
bill.expiredToken TTL hit, never settledRecorded as an inbound event
bill.cancelledPaymaster 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.

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; 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/bills for that merchant, so new receipts print without the SplitEasy QR.
  • Receipts already printed keep pointing at SplitEasy; whether a bill_token still 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.