Ezra Integration
How Ezra (the conversational AI partner) provisions brands and fires test conversions on Trcker. Service-key authenticated; bypasses the standard signup flow.
What it is
Ezra is the conversational front door for Trcker — brands and affiliates can launch and manage affiliate programs from Slack, iMessage, SMS, or Gmail without ever opening a dashboard. Behind the scenes, Ezra calls a small set of service-authenticated Trcker endpoints to provision and verify everything.
This page documents the Trcker side of that integration. If you're a brand using Ezra, you don't need to know any of this — Ezra handles it for you. This doc is for engineers operating the platform.
Authentication
All Ezra-callable endpoints use a single shared service key set on both sides:
``
EZRA_SERVICE_KEY=<32+ char random secret>
``
Set this on both: - Trcker (Vercel): so we accept Ezra's calls - Ezra (Fly.io): so it can sign outbound calls
Send via standard bearer header:
``
Authorization: Bearer <EZRA_SERVICE_KEY>
``
The validator uses constant-time comparison and fails closed (returns 503) if the env var is missing or shorter than 32 chars. See src/lib/auth/ezra-service-key.ts.
Endpoints
POST /api/public/brands/create
Provisions a brand on Ezra's behalf. Bypasses the free-email signup gate (Ezra-provisioned brands are vouched for by service-key auth). Idempotent on brandDomain — retrying with the same domain returns the existing brand.
Request body:
``json
{
"ownerEmail": "sarah@bedrockfit.com",
"ownerName": "Sarah Chen",
"brandName": "Bedrock Fitness",
"brandDomain": "bedrockfit.com",
"platform": "shopify",
"aov": 85,
"vertical": "fitness",
"payoutPercent": 0.47
}
``
platform is one of: shopify | woocommerce | bigcommerce | wix | stripe | custom. payoutPercent defaults to 0.47 (DTC standard) if omitted.
Response (`201 Created`):
``json
{
"data": {
"brandId": "uuid",
"brandSlug": "bedrock-fitness",
"brandApiKey": "trk_...",
"offerId": "uuid",
"offerSlug": "default-offer",
"platform": "shopify",
"shopifyInstallUrl": "https://trcker.io/api/public/install/shopify/start?brand_id=...",
"alreadyExisted": false
}
}
``
shopifyInstallUrl is null for non-Shopify platforms.
POST /api/internal/test-conversion
Fires a synthetic click + conversion to verify a brand's tracking pipeline is working end-to-end. Both rows are flagged is_test=true (migration 0035) so they're excluded from every reporting query — they never pollute EPC, payouts, or partner stats.
Request body:
``json
{
"brandSlug": "bedrock-fitness",
"offerSlug": "default-offer",
"amountCents": 8500
}
``
offerSlug defaults to the brand's first offer if omitted. amountCents defaults to the offer's revenueAmountCents.
Response (`200 OK`):
``json
{
"data": {
"passed": true,
"testRunId": "uuid",
"clickId": "uuid",
"clickDbId": "uuid",
"conversionId": "uuid",
"revenueCents": 8500,
"payoutCents": 4000,
"message": "Test conversion fired successfully. Tracking pipeline is live."
}
}
``
How it works
- A brand asks Ezra to launch an affiliate program
- Ezra calls
POST /api/public/brands/createwith the basic brand info → receives a brand record + default offer + brand API key - For Shopify brands, Ezra surfaces the returned
shopifyInstallUrlto the brand as a one-tap install link - After Shopify install completes (see Shopify Install), Ezra calls
POST /api/internal/test-conversionto verify tracking is working - On success, Ezra texts the brand: "Tracking is live."
The brand never opens a dashboard during onboarding. Trcker is the engine; Ezra is the conversation.
Trcker → Ezra handoff (connect tokens)
The flow above is the Ezra-first path. The Trcker-first path uses connect tokens: a brand operator who signed up on trcker.io directly can move to chat with one tap.
/brands/[brandSlug]/connect-ezra (UI)
Operator-only page on the Trcker dashboard. Renders two tabs — Slack and Email — and mints a connect token on click. We deliberately don't expose iMessage / SMS / per-provider email tabs: every channel added is another inbound-webhook signing scheme, another auth-token refresh dance, and another rate-limit profile to monitor. Slack covers live ops (most reliable API of the bunch + brand operators already live there) and Email covers async / approval-style work (universal, no app install). The two cover the brand-operator persona without the long-tail reliability cost.
| Channel | Deep link shape |
|---|---|
| Slack | https://<ezra-base>/install/slack?state=<token>&brand=<name> — OAuth start, token returned to Ezra via state. |
| Email | mailto:<connect-email>?subject=…&body=connect <token> — opens the user's default mail client; Composio routes Gmail vs Outlook server-side based on which provider they later connect. |
A 10-min countdown is displayed; "Generate a new token" re-mints if it lapses. A collapsible exposes the raw connect <token> string for manual paste.
Configurable via env vars (defaults are reasonable):
- NEXT_PUBLIC_EZRA_BASE_URL (default https://textezra.com)
- NEXT_PUBLIC_EZRA_CONNECT_EMAIL (default connect@textezra.com)
The page is linked from two places in the dashboard:
- Help section of the brand sidebar (Manage from your phone)
- Empty-state nudge on the offers list (Or set it up from chat →)
POST /api/public/connect-tokens/create
WorkOS-authenticated. The operator clicks "Manage from your phone" in the Trcker dashboard and the page calls this endpoint to mint a one-shot, 10-minute token bound to (brand_id, workos_user_id, channel).
Request body:
``json
{
"brandId": "uuid",
"channel": "slack" | "email"
}
``
Response:
``json
{
"data": {
"token": "<64 hex chars>",
"channel": "slack",
"expiresAt": "2026-05-11T20:30:00Z",
"ttlSeconds": 600
}
}
``
Operator-only (requireBrandOperator) — partners can't mint tokens for the brand. Audit-logged via Sentry. Token is the only handle that crosses systems; leaking it leaks at most "join this brand from one channel for the next 10 minutes."
POST /api/public/connect-tokens/verify
Service-authenticated (Ezra). Atomically consumes the token and returns the data Ezra needs to provision the channel ↔ brand binding on its side. The token travels in the request body (not a query string) so it never lands in access logs, Sentry's request.url capture, or downstream log drains.
Request body:
``json
{ "token": "<64 hex chars>" }
``
Response:
``json
{
"data": {
"brandId": "uuid",
"brandSlug": "bedrock-fitness",
"brandName": "Bedrock Fitness",
"brandDomain": "bedrockfit.com",
"brandApiKey": "trk_…",
"ownerEmail": "sarah@bedrockfit.com",
"ownerName": "Sarah",
"channel": "slack"
}
}
``
Status codes:
- 200 — token valid, just consumed
- 400 — malformed body / bad token shape
- 401 — service auth failed
- 410 Gone — token unknown, expired, or already consumed (don't distinguish — Ezra should just ask the user to generate a new one)
Consumption is an atomic UPDATE … WHERE consumed_at IS NULL AND expires_at > now() — two concurrent verify calls cannot both succeed.
Test conversions are excluded from reports
Migration 0035 added is_test BOOLEAN NOT NULL DEFAULT false to both clicks and conversions. Every report query in src/lib/db/queries/reports/* filters is_test = false. Test conversions:
- Don't count toward partner EPC
- Don't trigger payouts to partners
- Don't appear in attribution dashboards
- Don't fire CAPI events to ad networks
Safe to fire as often as needed during install verification.
Operational notes
- Brand provisioning audits via
Sentry.captureMessage("Ezra brand provisioned", level: "info")— searchable in Sentry by tagroute:public/brands/create - WorkOS Organization is created for every Ezra-provisioned brand so the brand can later log into trcker.io directly via SSO
- Service-key rotation: change
EZRA_SERVICE_KEYon both Trcker and Ezra simultaneously; existing brand API keys remain valid - No retry/backoff is built into the endpoint — Ezra is responsible for retrying on 5xx