Zoul → Central Affiliate Platform — Integration Spec (v1.2 FINAL)
Zoul sends per-user events only. All eligibility, Net Growth and commission math runs centrally on the affiliate platform. Zoul never sends counts, totals or amounts.
1. Affiliate links (AppsFlyer OneLink)
Base OneLink (production, no affiliate code embedded):
https://zoul.onelink.me/X33D/de7q9ov9
Dynamic parameter: ref={AFFILIATE_CODE}
Final affiliate URL — the affiliate platform appends the ambassador's real code:
https://zoul.onelink.me/X33D/de7q9ov9?ref={AFFILIATE_CODE}
Example:
https://zoul.onelink.me/X33D/de7q9ov9?ref=YUI
The parameter is exactly ref. The code always comes from the affiliate system —
never hardcoded. The Affiliate Dashboard Copy / Share action for Zoul produces
exactly this URL.
Delivery of ref (confirmed by Zoul Dev)
| Scenario | Mechanism | ref received |
|---|---|---|
| App already installed | AppsFlyer Deep Link | Yes |
| New install | AppsFlyer Deferred Deep Link / First Open | Yes |
Zoul stores the received ref value against the user and sends that value to Central
as affiliate_code in the user events below. ref is customer-facing only —
the API contract field name stays affiliate_code.
2. Endpoint
POST https://lescal.com/api/apps/6a06fc7184e0c5172c32d204/functions/zoulUserEvent
Content-Type: application/json
Headers (all required)
| Header | Value |
|---|---|
x-api-key | Zoul API key (issued separately, out of band) |
x-timestamp | Unix seconds, must be within 5 minutes of server time |
x-signature | HMAC_SHA256(api_key, "<timestamp>.<raw json body>"), hex |
The signature is computed over the exact raw body string that is sent.
3. Events
| Event | When to send | Effect |
|---|---|---|
UserVerified | user completes verification / qualification (once per user) | marks user verified (never reset) |
UserActivity | qualifying session / activity | updates last_activity_at |
AccountDeleted | account deleted / closed | user excluded from eligibility |
Body
{
"website": "zoul.app",
"event": "UserVerified",
"conversion_id": "zoul_evt_000123",
"external_user_id": "zoul_user_4821",
"affiliate_code": "YUI",
"event_at": "2026-08-13T08:00:00Z"
}
| Field | Required | Notes |
|---|---|---|
website | yes | always zoul.app |
event | yes | one of the three above |
conversion_id | yes | unique per event; resending the same id is ignored (idempotent) |
external_user_id | yes | permanent, stable user id — same across devices and sessions |
affiliate_code | on first event | the value received from ref in the OneLink |
event_at | recommended | ISO 8601 UTC; defaults to receipt time |
UserActivity should be sent at most once per user per day.
4. Attribution rules
- The affiliate is locked on the first event carrying a code and is never changed.
- A later event with a different code is accepted, the user keeps the original affiliate, and an attribution-conflict counter is incremented for audit.
- Events without a code for an already-known user are fine.
- Attribution is keyed on
external_user_id, so a reinstall or new device never creates a second attribution for the same user.
5. Responses
| Status | Meaning |
|---|---|
| 200 | accepted (duplicate: true when the conversion_id was already processed) |
| 400 | missing / invalid field or unsupported event type |
| 401 | bad API key, bad signature, or stale timestamp |
| 403 | event type not enabled for this website |
| 500 | server error — safe to retry with the same conversion_id |
Example success:
{ "success": true, "attribution": "locked", "affiliate_code": "YUI",
"verified": true, "deleted": false, "duplicate": false, "commission_amount": 0 }
commission_amount is always 0 — commissions are never created by this endpoint.
6. Commission model (platform side, for context)
- A user is eligible when: verified AND not deleted AND active within the activity window (default 30 days).
- On the 15th and 30th of each month the platform calculates, per ambassador:
net growth = eligible now − high-water mark (HWM). - Commission is paid only for positive net growth, once per user, at the per-user amount defined in Commission Rules.
- The HWM only ever increases — churn never creates a negative balance, and regained users are never paid twice.
7. Retry guidance
Retry on network errors and 5xx with exponential backoff, reusing the same
conversion_id. Duplicates are safely ignored.