Skip to main content
Signup Recovery calls people who create an account in your app and never buy. Your backend posts us the signup. We wait, then check RevenueCat or Stripe to see whether they’ve purchased since. If they still have not, we text a heads-up and call them with your win-back offer.
Signup Recovery needs a RevenueCat or Stripe account connected, the same one used for cancellation recovery. See Connecting RevenueCat or Connecting Stripe.

The request

Your backend posts one event per signup:

Where to place the call

Where you call the webhook decides when we start. The event is the trigger. We wait 5 to 60 minutes, then check whether the person has paid. We call only if they have not. We never call a signup older than 48 hours. So send the event at the buy decision, not before it. Send the event at the paywall, the moment someone sees your price and does not buy.

Fields

The minimum event

The smallest event that produces a call depends on your billing provider:
  • RevenueCat: phoneNumber and consent.attested: true, plus appUserId
  • Stripe: phoneNumber and consent.attested: true, plus email or stripeCustomerId
The identity field is what lets us confirm the person never paid. RevenueCat answers only for an app_user_id. Stripe answers only for a Customer id or an email. hyzl stores an event that has a phone number but no identity, never verifies it, and never calls the person.
appUserId must be the same ID your app passes to RevenueCat’s Purchases.logIn(). If it is anything else, the purchase check silently matches nobody. Every signup then reads as unpaid, and hyzl can call someone who already paid.
If you use Stripe, set client_reference_id or metadata.userId on the Checkout Session. Use the same ID you use for this person elsewhere, for example the appUserId you send us. Stripe has no equivalent of Purchases.logIn(). Without one of these, we can only match on stripeCustomerId or email, and email is the weakest match we have.
The Stripe check looks for subscriptions, including cancelled and expired ones. A one-time Stripe payment does not count as paid. A member whose only purchase is a one-time product can still get a call.

Get your webhook URL and ingest token

  1. Go to Revenue Recovery → Workflows and click New workflow
  2. Choose “A user signs up but does not pay” as the trigger
  3. Connect (or choose) whichever provider your purchases run through: RevenueCat or Stripe
  4. Set your win-back offer, the wait time, the daily call cap, and the calling window
  5. Copy your webhook URL, your ingest token, and the copy-paste snippets in curl, Node, and Swift
  6. Tick the consent acknowledgment. Nothing turns on until you do, here or from the Workflows list
  7. Finish the campaign (script, voice, schedule) and hit Finalize & Launch
Send the URL and ingest token to your developer. Or hand them the developer bundle described at the end of this page.

Send the event

Send it in steps

Most apps do not have a phone number at signup. Send what you have, then send the rest once you have it. Use the same appUserId on every step. We assemble the record for you, joining on appUserId first, then email, then phone number, whichever the two requests share.
Node
A later step never erases an earlier field. Sending step 2 without firstName does not blank out a firstName step 1 already sent. Add a checkbox next to where you already collect the phone number. Here is the wording we provide:
This checkbox lives in your signup form. We supply the wording. We never render it ourselves. Replace “Your company” with your own name. Each clause is doing a job:
  • Put the checkbox wherever you already collect the phone number.
  • Keep the phone field optional. The signup must finish without it, because consent cannot be a condition of purchase.
  • Leave it unchecked by default. A pre-ticked box is not consent.
  • Send us its real state as consent.attested, true or false. Do not send true when it is unchecked.
  • We store signups that arrive without consent, and we never call them.
If your signup flow has no phone field today, add one at the paywall step. Keep it optional, and the checkbox with it.

Wait time and re-enrollment

After we receive a phone number and consent.attested: true, we wait before checking whether the person has paid. The default is 5 minutes, adjustable from 5-60 minutes when you set up the workflow. The wait is not what protects a paying member from a call. RevenueCat and Stripe both know about a purchase within seconds, and we re-check before we queue the call. The wait mainly absorbs ordinary delivery delays on your side. There is one gap worth knowing about. When a day’s calls hit your daily cap, the remaining calls are held to the next day. The paid check for those ran before they were held. Someone who buys overnight, in that window, can still be called the following morning. Purchases that reach us through RevenueCat or Stripe while a call is still queued do cancel it. So this only affects a purchase we hear about after the call has left the queue. If the same person hits your paywall again, we do not restart the wait. Once someone has been contacted, resolved, or parked, a new event from them starts a fresh cycle. That cycle starts only after 30 days. Inside that window, hyzl treats it as the same signup.

How many calls, and when

Two settings on the same screen as the offer and the wait time. Both apply to this workflow only, and both can be changed later from Edit on the workflow. Daily call cap. The most calls this workflow places in one day. The default is 100. hyzl does not drop signups past the cap. It calls them the next day instead. Calling window. The hours we call, in each member’s own local time, every day of the week. The default is 9am to 8pm, and that is also the widest it goes. You can pull either end in, for example 10am to 6pm. You cannot push either end out, and there is no out-of-hours override. A narrower window does not mean fewer calls, it means later ones. A signup whose wait ends at 8am is held until the window opens rather than dialed early.
Both settings are written to the workflow when you create it, so it runs under them from the first call. If you never open them, you get 100 calls a day between 9am and 8pm.

When nobody answers

A person who never answers still gets the offer. Once every dial has gone unanswered, hyzl checks with your provider one final time. If the person has still not paid, hyzl texts them the offer once.
  • On Stripe, the text carries a discounted checkout link. The link is minted at send time and expires 24 hours later. The expiry is Stripe’s own, so it is a real cutoff.
  • On RevenueCat, the text carries an App Store redemption code from your offer-code pool.
This text needs a positive win-back discount. A 0% workflow has no offer, so it sends nothing after a missed call. If the offer text goes unredeemed, hyzl sends one reminder text about 23 hours later. That is an hour before the claimed deadline, the same as the other recovery triggers. A member who replies to either text reaches your AI receptionist. It can resend a still-valid link on request. See Offer Texts for the send policy and wording. The default voicemail matches. When a text will follow, it says hyzl will text a link to finish signing up. A voicemail script you edited yourself is never changed.

Responses

Treat both 200 and 202 as success. Anything else is a real failure worth logging. A 202 means we stored the event, not that we called anyone. We wait, check, and call only after both checks pass. A 200 means we stored it but scheduled nothing, because the workflow is switched off. A draft workflow is off until you press Finalize & Launch. So your developer will see this on every request until then. A check written as if (status !== 202) throw reports a failure on a request that worked. Every response also carries flowEnabled, contactable and consentAttested, plus a note naming each thing currently blocking a call. Those are the fields to assert on in a smoke test.

While a workflow is switched off

A workflow is off before you launch it, and off again whenever you pause it. hyzl stores events you send in the meantime, so they still count in your reporting. But nothing is scheduled for them, and nothing is called. Turning the workflow on does not call them either. We only call signups that arrive while it is on. A pause is not a queue. A three-day pause does not end in three days of calls landing at once. This is deliberate. It is the same rule we apply to any signup older than 48 hours. Past that point, a call stops being a nudge about a decision someone is still making. To test a live workflow, turn it on and send a fresh signup.

Where your signups show up

Every signup you send gets a row in the Signup recovery card on Revenue Recovery → Activity. The row carries the date, the person, one sentence on what happened to them, and an outcome badge. That card has its own search, date filter, and outcome filter. None of them touch the cancellation table above it. Click a row to open the person’s profile, with the call recording and the transcript. See Activity Log.

If it is not working

Check the Signup Recovery workflow card in Revenue Recovery → Workflows. It warns you when something is off with the integration and names the exact problem. For example:
  • A run of events with no appUserId
  • A phone number it could not parse
It also shows how many events the problem affected and when it last happened. Fix the field the warning names, and the next event clears it.

Paste it into an AI coding assistant

The setup step in the portal also has a developer bundle. One copy button produces a self-contained brief covering:
  • The task
  • Where the POST goes
  • The consent checkbox
  • The field table
  • A ready request with your real URL and credential filled in
  • How to confirm it is working
Paste it into Claude, Cursor, or any AI coding assistant with “do this.” It has everything needed without seeing the rest of these docs.