Zupy is an OpenDelivery 2.0-compliant participant
Zupy is OpenDelivery 2.0 compliant for the Customer (Loyalty) capability. If your
platform already speaks OpenDelivery, you can integrate with Zupy loyalty using the standard
— discover us via our manifest, authenticate with
client_credentials, and receive signed
events. No Zupy-proprietary contract required.What OpenDelivery is
OpenDelivery is an open Brazilian standard that defines how ordering platforms, delivery logistics, and merchant software interoperate. Zupy participates as a Customer capability provider (loyalty programs, accounts, transactions, rewards, coupons, and redemptions), so any OpenDelivery-aware ordering application can read a customer’s loyalty state and issue redemptions through a documented, non-proprietary interface.1. Discovery — find us via the manifest
Zupy publishes a public Discovery manifest at the well-known path. It advertises our protocol version, authentication models, and the exact operations we support — no need to coordinate out-of-band.Discovery request
Manifest (live)
appId is Zupy’s single, stable participant identity — the same value appears in every
webhook we send (X-App-Id). It is not per-partner.2. Authentication — two models, both standard
OpenDelivery allows a provider to declare more than oneclientIdGeneration model. Zupy declares
both, so you pick the one that fits your integration:
by_app (recommended)
One credential for your application, valid across all merchants linked to it. Best
for platforms that integrate many Zupy merchants. Each merchant is activated by the merchant
themselves via a toggle in their Zupy dashboard. Provisioned by the Zupy partnerships team.
by_merchant (self-serve)
One credential per merchant, provisioned by the merchant from their own dashboard
(
/integrations). Best when a single merchant wants to connect an app Zupy hasn’t formally
partnered with yet. No partnership required.client_credentials OAuth2 grant to obtain a bearer token, and both are
exposed identically at https://api.zupy.com/v2.
Selecting a merchant with by_app
A by_app token can reach multiple merchants, so each request must name which merchant it targets —
either with the X-App-MerchantId header or the ?storeId= query parameter (equivalent; same
value). Zupy only serves data for merchants whose toggle links your application — anything else is
denied.
string (header)
The merchant identifier as your application knows it (the OpenDelivery
merchantId). Zupy
maps it to the merchant internally.string (query parameter)
Same value and semantics as the header, as a query parameter — the store filter proposed for the
standard in opendelivery-v2#24
(precedent:
Order.storeId / listOrders). Use whichever fits your client.3. Merchant identity — two-sided by design
OpenDelivery models a merchant with two identifiers, and Zupy stores both:
Call
GET /v2/merchants with your token to list every merchant your credential can reach, each
returned with both identifiers.
4. Supported operations
The Customer/Loyalty operations are live athttps://api.zupy.com/v2, at the exact spec
paths (a trailing slash is tolerated). List operations are paginated
({ items, page, pageSize, total }).
Resolving a customer is order-driven: send a phone/email to
listCustomers
(customerIdentifierType + customerIdentifierValue) to get the customerId, then read
/customers/{customerId}/loyalty-accounts. Registration itself happens automatically when you
report an order (accrual) — see §6.Error shape. Errors to an OpenDelivery partner are returned as
{ "code": "...", "message": "...", "details": [...] } (the OpenDelivery ErrorResponse;
details is an array of strings). HTTP codes: 400 (validation), 401 (auth), 404
(not found), 409 (conflict), 429 (rate limit), 500.5. Webhooks — signed OpenDelivery events
When loyalty state changes, Zupy sends a signed event to your registered webhook URL. Events use the OpenDelivery envelope (id, eventType, occurredAt, accountId, customerId,
programId, payload).
Why only four events? The OpenDelivery
LoyaltyEventType enum is closed — the standard
defines exactly five values, and Zupy emits the four that apply to its loyalty model
(loyalty.cashback.earned is not used; Zupy has no cashback). Zupy never emits an eventType
outside the spec enum.Zupy’s native webhook surface carries nine event types. The
mapping:Extension events (proposed upstream)
The three extensioneventTypes above announce transitions the OpenDelivery schema already
models (LoyaltyTransactionType.adjust; LoyaltyCoupon.status → expired/cancelled) but the
event enum does not cover — including the points reversal triggered by the standard’s own
order.cancelled flow. We proposed them upstream in
Abrasel-Nacional/opendelivery-v2#25;
until adopted, they are a vendor extension.
They use the same envelope, headers, and signature as the standard events, but are never
delivered by default: a subscription with empty webhook_events (= “all”) receives only the four
spec-enum events. To receive an extension event, list it explicitly in your webhook_events
(e.g. ["loyalty.points.earned", "loyalty.points.adjusted"]).
Every webhook carries these headers:
string
Zupy’s participant
appId — identical across all our webhooks.string
The merchant this event belongs to (the
merchantId you assigned).string
Lowercase hex HMAC-SHA256 of the raw request body, signed with your webhook secret — no
sha256= prefix. Verify it before trusting the payload.6. Inbound — accrue points from orders
If your platform owns the orders, you can have Zupy award loyalty points automatically when a sale happens — and reverse them if that sale is cancelled. Zupy is the loyalty host: you send two signals, we do the rest (we never track your order lifecycle — when to send each signal is entirely your call). Send an OpenDeliveryCustomerEventEnvelope to:
Accrue / reverse
Both are authenticated with the same OAuth2 credential you use for reads (
by_app or
by_merchant) — no separate scheme. A minimal envelope:
order.created
object
{ type, value } — type is phone or email (a document/CPF-only customer is skipped, no
lookup). The customer is enrolled automatically if new.number
Order total in cents (OpenDelivery convention). Zupy converts to the base currency before
applying the points rate.
Idempotent by
orderId. Re-sending the same order.created never double-credits; re-sending
order.cancelled never double-reverses. A cancel for an order that was never credited is a no-op.Accrual is opt-in per merchant. A merchant must have an OpenDelivery accrual rate configured;
until then,
order.created is accepted (204) but awards nothing. When points are awarded, Zupy
emits the loyalty.points.earned webhook back to you (§5).Reference
OpenDelivery spec
The normative OpenDelivery 2.0 specification.
Partner Onboarding
What you receive and how to go live.
Outbound Webhooks
Webhook delivery, retries, and signature verification in depth.
Get a credential
Contact the Zupy partnerships team for a
by_app credential.