Docs/Integrations

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

  1. A brand asks Ezra to launch an affiliate program
  2. Ezra calls POST /api/public/brands/create with the basic brand info → receives a brand record + default offer + brand API key
  3. For Shopify brands, Ezra surfaces the returned shopifyInstallUrl to the brand as a one-tap install link
  4. After Shopify install completes (see Shopify Install), Ezra calls POST /api/internal/test-conversion to verify tracking is working
  5. 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 tag route: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_KEY on 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