Skip to main content
Send order and customer data to Zupy for automatic loyalty processing. Webhooks handle customer enrollment, points calculation, and deduplication — all asynchronously.
Prerequisites: Your API key (zupy_pk_*) and integration slug (e.g., repediu). See Getting Started if you don’t have these yet.
Two webhook surfaces — make sure you’re on the right one. This page covers the inbound flow: your system → Zupy. There’s a separate outbound flow (Zupy → your system) where Zupy notifies you when a customer earns points, redeems a coupon, etc. — see Outbound Webhooks. The URLs are deliberately distinct:
  • 🟢 Inbound (this page): POST /api/v2/webhooks/integrations/{partner}/ — you call this; Zupy ingests.
  • 🟠 Outbound: GET/PUT /api/v2/integrations/webhooks/ — you configure where Zupy should call.

How It Works

Partners send order data to Zupy via a single webhook endpoint. Zupy processes each order asynchronously:
  1. Receive — Zupy validates your API key, stores the raw payload, and returns immediately
  2. Deduplicate — SHA-256 hash of the request body prevents double-processing
  3. Process — A background worker identifies customers, calculates points, and enrolls new users
  4. Enroll — Customers not found by phone/email/CPF are automatically created and enrolled in the restaurant’s loyalty program

Endpoint

  • {partner} is your integration slug (e.g., repediu, saipos, goomer)

Headers

Payload Format

The webhook accepts any JSON payload. Zupy stores the raw payload and processes it using a partner-specific adapter.
Each partner has their own payload format. The adapter maps your fields to Zupy’s internal data model. Below is the Repediu format as a reference — your format may differ.

Example: Repediu Payload (JSON Array of Orders)

Send a JSON array of sale objects in a single POST:

Repediu Field Reference

At least one customer identifier (Celular, Email, or CpfCnpj) must be present. Orders without any identifier are skipped during processing.

Sending Webhooks

Response Format

New Webhook (200)

Duplicate Webhook (200)

Duplicates return HTTP 200 — not an error. Your integration does not need special error handling for re-sent payloads.

Idempotency

Zupy computes a SHA-256 hash of the raw request body for every webhook. If the same payload is sent twice:
  • The second request returns "status": "duplicate" with HTTP 200
  • No duplicate processing occurs
  • The original id is returned so you can reference it
This means you can safely retry failed sends without worrying about double-processing.
Webhook-level deduplication (SHA-256):
  • Every incoming request body is hashed with SHA-256
  • The hash is stored as idempotency_key in the WebhookEvent record
  • If a matching hash already exists, the request is treated as a duplicate
Order-level deduplication (per adapter):
  • Each order’s unique ID (e.g., Repediu’s id_venda) is checked before awarding points
  • If points were already awarded for that order, the order is skipped silently
  • This protects against reprocessing if the same orders appear in different batches

Async Processing

Webhooks are processed asynchronously:
1

Immediate Response

The webhook endpoint validates your API key, stores the raw payload, and returns HTTP 200 immediately. No processing happens at this stage.
2

Background Queue

A Celery worker picks up the stored event and processes it using the partner-specific adapter. This typically happens within seconds.
3

Per-Order Processing

Each order in the batch is processed independently:
  • Extract customer identifier (phone > email > CPF)
  • Find or create the customer in Zupy
  • Enroll in the restaurant’s loyalty program (if new)
  • Calculate and award points based on the order value
One bad order does not block the batch. Each order is processed independently. If one order fails (e.g., missing identifier), the rest continue normally.

Batch Processing

Send multiple orders in a single request. Each order is processed independently:
In this example:
  • Order 100 (Maria): Processed — customer found/created, points awarded
  • Order 101 (João): Processed — customer found/created, points awarded
  • Order 102 (Não Informado): Skipped — no phone, email, or CPF to identify the customer

Error Handling

Error Response Format (RFC 7807)

Rate Limits

Webhook requests are subject to two rate-limiting layers: Gateway level (per IP address, applied by APISIX): Consumer level (per API key, based on your tier): Both limits apply simultaneously. The lower of the two is your effective limit.
Best practice: Send orders in batches (arrays) rather than one order per request. A single batch counts as one request regardless of how many orders it contains.
When rate-limited, you receive a 429 response with X-RateLimit-Remaining and Retry-After headers indicating your remaining quota and how many seconds to wait.

Best Practices

Send multiple orders in a single array rather than one request per order. This reduces API calls and stays well within rate limits. A batch of 100 orders is a single request.
Webhooks are idempotent. If a request times out or fails, retry with the same payload. Zupy detects duplicates automatically — no risk of double-processing.
Orders without any identifier (phone, email, or CPF) are skipped during processing. Ensure at least one identifier is present whenever possible.
Include a unique order ID in your payload (e.g., Repediu’s id_venda). This enables order-level deduplication and makes debugging easier.
The webhook returns immediately. Processing happens asynchronously in the background (typically within seconds). Don’t poll for results — use the Customer API to verify data after a short delay.

Next Steps

OTP Flow

Learn about customer identity verification for sensitive actions

Partner Onboarding

Complete the onboarding checklist for production deployment

API Reference

Browse all endpoints with request/response schemas