Skip to content
OSOKORO

Embedding a waitlist in a site you already have

Hosted page, embed, or your own form against the API — what each costs you in control, and the export trap that silently loses rows.

Published Aug 22, 20264 minutes to read

If you already have a site, a hosted waitlist page is a second URL competing with the one you built. Three ways to avoid that, with different tradeoffs.

The three options

The hosted page. A URL we serve. Zero integration, works immediately, and it is the right answer if the "site you already have" is a single landing page you would happily replace. Available on every plan including Free.

The embed. Our form inside your page. You keep your design, your domain, your analytics if you have any. Starter ($10/mo) and up.

Your own form against the API. Complete control over markup and validation, and you own every edge case. POST the signup, handle the response. Note that the API is on Everything ($20/mo) — Starter has apiRequestsPerHour: 0, which is a real constraint rather than a soft limit, and it catches people who assume paid means API.

Most people want the embed. The rest of this is about the parts that are not obvious.

What the embed handles that your own form will not

Writing the form yourself looks like an afternoon. The afternoon is real; the following month is the cost.

  • Double opt-in, including resend, expiry, and the case where somebody signs up twice.
  • Disposable-domain blocking and CAPTCHA.
  • Referral attribution, including the abuse patterns that appear within a day of a queue position being worth something.
  • Position assignment that stays consistent when signups arrive concurrently.
  • Unsubscribe and suppression, which is a legal obligation rather than a feature.

None of those are hard individually. All of them are things you will get slightly wrong and discover through a support email.

Build your own form when you have a genuine reason — an unusual flow, a field the product does not support, a compliance requirement about what touches a third party. Not to save an afternoon.

Custom domains, and the honest split

Two different things share a similar name and only one of them exists.

Custom page domain — serving your waitlist at waitlist.yourdomain.com — is live. It was proven end to end against a real domain, all ten steps. Worth knowing that it was broken for its entire prior life because of a wrong Vercel API route, and nobody noticed until it was verified deliberately, which is a decent argument for testing the thing rather than reading the readiness table. Starter and up, 1 domain on Starter, 3 on Everything, 25 on Studio.

Custom sending domain — email arriving from your domain — reads "Coming soon". Our SES account is in the sandbox. Mail sends today from the shared domain on every plan.

The practical consequence: you can have your page on your domain and your email arriving from somewhere else. That mismatch measurably costs verifications, so make the confirmation email's subject line and preview text unmistakably yours.

Getting the data out

Three routes, and one shared trap.

CSV export. Starter and up. Streams by keyset.

GET /api/v1/signups. Everything and up. Keyset pagination, updated_since for incremental sync, per-hour quotas, and a stable error shape. Page size is 1 to 500, defaulting to 100.

Webhooks. Everything and up. Five events — signup.created, signup.verified, signup.unsubscribed, perk.claimed, broadcast.sent — signed, up to 20 HTTPS endpoints per organisation.

Now the trap, which is the reason this section exists.

PostgREST caps a query at 1,000 rows by default. A select written to mean "give me everything" returns the first thousand, with no error and no warning. In this codebase it truncated a CSV export and the validation analytics before anybody noticed, because the file downloads, opens, and looks entirely correct.

That is why the export streams by keyset and why the API paginates by keyset rather than offset. If you are querying a Supabase or PostgREST-backed database of your own, this will happen to you, and the symptom is a number that is quietly wrong rather than an error you can find.

Offset pagination has a second problem worth knowing: rows shift between pages as new signups arrive, so you get duplicates and gaps. Keyset does not, which is why updated_since exists.

Which fields you actually get

Whatever the form collected — address, verification state, position, source, referral attribution, and your custom questions. Custom questions are Starter and up; on Free you get the address and the mechanics, which is enough to run the experiment and not enough to run the survey.

Rough decision

  • A landing page you would replace anyway — hosted page. Free.
  • An existing site you want to keep — embed. Starter, $10/mo.
  • You need the data in your own systems — embed for collection, API or webhooks for the data. Everything, $20/mo.
  • A genuinely unusual flow — your own form against the API, and read the list of things the embed handles before committing to it.

Read next

All writing