> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hyzl.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up with an AI code assistant

> Give Claude Code, Codex, or Cursor the exact prompt for a safe Hyzl setup.

Use this option if Claude Code, Codex, or Cursor can edit your app's code.
The assistant finds the paywall, adds the paywall event, and helps you test it.

1. Open your app's repository in your AI code assistant.
2. Open the Install page in Hyzl.
3. Copy the setup prompt from Hyzl.
4. Paste the prompt into your assistant.
5. Follow the assistant's requests for your Ingest token and test phone number.

Hyzl fills the placeholders before you copy the prompt. The prompt never contains your Ingest token.

Your assistant asks for the Ingest token only when it can store the token in your server environment.
The Ingest token must never appear in a chat, code file, log, or screenshot.

## The prompt

```
You are integrating Hyzl paywall recovery into this codebase. Hyzl texts people
who leave our paywall without paying, from our own number, after checking that
they still haven't paid. Your job is to send Hyzl one event at the right moment.
Work in this repo, make the change, and stop before anything that would text a
real customer.

FACTS
- App: {{APP_NAME}} (Hyzl app id {{APP_ID}})
- Endpoint: POST {{WEBHOOK_URL}}
- Auth: header `Authorization: Bearer <server key>`, where the server key is the
  one credential that lets our backend post paywall events to Hyzl, read from
  the environment variable HYZL_INGEST_TOKEN on our server. Anyone holding it
  can make Hyzl text any phone number, so it stays on the server: never print,
  log, commit, or hardcode it, and never send it from a browser or a mobile app.
- Payment provider connected in Hyzl: {{PROVIDER}}
- Link mode: {{LINK_MODE}}{{PAYWALL_URL_LINE}}

RULES
1. I copy the server key from the Hyzl Install page. Store it as
   HYZL_INGEST_TOKEN with your platform's secret command reading from the
   clipboard (macOS `$(pbpaste)`, Windows `Get-Clipboard -Raw`, Linux
   `xclip -selection clipboard -o`) so the value never appears in this
   conversation, in code, in `.env.example`, in logs or in tests. Ask me to
   copy it when you reach this step, confirm the variable exists by name only,
   and never print it.
2. The event is sent from our SERVER, never from client code.
3. consent.attested is the real state of whatever consent our app collects,
   true or false. Never hardcode true: a wrong true means texting someone who
   never agreed. Absent leaves what Hyzl already holds unchanged; an explicit
   false revokes.
4. While you work, only send events with `test: true` and MY phone number, or
   for my own test account. Do not send events for real users until I say the
   app is live in Hyzl.
5. Nothing you add may break the user's flow: the call runs after the response
   or in the background, never throws into the UI, has a timeout, and logs
   only the HTTP status and Hyzl's `error` code.

TASKS
1. Find the paywall. Locate where the app shows the price and where it can tell
   the person left without buying (closed, dismissed, navigated away, or a
   short timeout after render). That moment is the trigger. If the app cannot
   detect leaving, fire when the paywall renders; Hyzl waits before texting, so
   people still deciding are absorbed. Never fire at signup or when a phone
   number is collected: that is too early. Tell me which file and function you
   chose and why.
2. Find the durable identity.
   - RevenueCat: `userId` must be EXACTLY the value passed to
     Purchases.logIn(). Anything else silently matches nobody.
   - Stripe: send `stripeCustomerId` (cus_…) when we have one, and make sure our
     Checkout Session sets `client_reference_id` or `metadata.userId` to the
     same `userId` we send Hyzl, so payments can be matched.
   Email and phone alone are not enough; Hyzl rejects such events.
3. Find the phone number and consent. Locate where we collect the phone number
   and how we already ask permission to text people, with its timestamp if we
   store one. If we already collect consent for texts in any form, use it as it
   is and send its real state — do not rewrite or redesign it. Only if there is
   no consent anywhere, suggest adding a checkbox next to the phone field, and
   offer this wording as a starting point I can edit (adjust the company name):
   "I agree that {{APP_NAME}} and its service providers may contact me by phone
   call and text message at the number provided, including with automated or
   AI-assisted technology, about my account and related offers. An AI may answer
   calls, and calls may be recorded and transcribed. Consent is not a condition of purchase. Message
   frequency varies. Message and data rates may apply. Reply STOP to opt out.
   Reply HELP for help. {Terms link} · {Privacy link}"
   Link our real Terms and Privacy Policy where the {placeholders} are. Suggest
   leaving the box unchecked by default and keeping the phone field optional,
   and store the wording shown and the time it was ticked.
4. Implement a server-side function, e.g. sendHyzlPaywallEvent(user, context),
   that POSTs this JSON with the auth header above:
   {
     "userId": "<durable user id>",
     "stripeCustomerId": "<cus_… if Stripe and known>",
     "phoneNumber": "<E.164, e.g. +13105551212>",
     "firstName": "<first name if known>",
     "email": "<email exactly as stored, if known>",
     "consent": { "attested": <true|false>, "text": "<wording shown>", "at": "<ISO 8601 when ticked>" },
     "eventId": "<stable id for this paywall exit, so a retry is a no-op>",
     "occurredAt": "<ISO 8601 or epoch MILLISECONDS>",
     "stage": "saw_price",
     "price": <number if known>, "currency": "<ISO code if known>",
     "context": "<optional: short facts about this person, e.g. onboarding answers; max 1000 characters>",
     "detail": "<optional short line, e.g. the trip they searched>",
     "recoveryUrl": "<https link to send this person, only if link mode is per_person>"
   }
   Normalize the phone number to E.164 before sending; drop the event if it
   cannot be normalized. Build eventId from the user id plus the paywall
   session or the UTC date, so one person hitting the wall four times in an
   evening is one event, not four. Timeout 10 seconds. Treat 200 and 202 as
   success. On 4xx read the `error` field; the codes are listed under
   RESPONSES. Retry only on 5xx or network errors.
5. Wire the trigger from task 1 to that function: client tells our backend
   the paywall was shown/left (or the backend already knows), the backend calls
   Hyzl. If the phone number arrives later than the paywall, send the event
   again with the same userId once you have it; the event carrying the phone
   number and consent is the one that starts the wait.
6. Test, in this order:
   a. Ask me for my phone number. Send one event with `test: true`, that
      number, and consent.attested true. Expect 202 and a text on my phone
      within a minute (test events skip the wait and the payment check).
   b. Send one normal event for my own test account. Expect 202 with
      "status" in the body; nothing is texted until the wait passes and the
      app is live.
   c. Tell me to open Hyzl's Install page: the Installation check must say the
      event was received.
7. Report: files changed, where the trigger fires, how identity and consent are
   captured, where you stored the server key (by variable name, never its
   value), anything you could not find, and what remains for me (any environment
   still missing HYZL_INGEST_TOKEN, turning the app live in Hyzl).

RESPONSES
202 stored (body: person_id, status, contactable, consent_attested,
next_action_at). 200 duplicate eventId, no-op. 400 malformed_body,
invalid_phone, invalid_consent (attested must be a boolean), missing_durable_id
(no userId and no cus_…), invalid_recovery_url (https only, no credentials,
under 2048 chars), invalid_percent_off (1–90), invalid_occurred_at (seconds
sent where milliseconds are expected lands here), test_requires_phone. 401
unauthorized (token). 500 ours, retry.

WHAT HYZL DOES AFTER THE EVENT
With the default text flow, waits 5 minutes, re-checks the billing provider,
and if the person is still unpaid and consented, texts once from our number
as {{APP_NAME}}'s AI rep. Default follow-ups at 1 and 3 days. Flow supports up to 6 steps (texts
or calls), with payment checked before every step. A reply pauses the sequence
and the AI rep answers; a payment or STOP ends it. Quiet hours apply to texts. Hazel answers calls on the same number and can also call people who consented
if calls are turned on in Flow. Calls are off by default.
```

The source above uses placeholders because Hyzl creates a separate prompt for each app.
For example, Hyzl replaces `{{APP_NAME}}` with your app's name.

If you prefer a manual setup, follow [Send the paywall event](/install/send-the-event).
For every available field, see the [paywall event reference](/reference/paywall-event).
