If you've shipped a SaaS product, you've probably built onboarding twice: once as a quick React form that posts to your API, and again six months later when you need versioning, webhooks, and "wait, why did we get three duplicate signups from the same user?"
Intake Relay is the second build — the boring infrastructure part — so you don't have to write it again.
It's a hosted API for structured intake: signup wizards, onboarding steps, partner applications, anything where you collect answers in your product and need them validated, stored, and forwarded to the rest of your stack. You keep the UI. We handle the schema, the rules, and the plumbing.
Flows and versions. A flow is one intake process ("SaaS onboarding", "agency application"). Each flow has numbered versions. You draft, edit, publish. Published versions are immutable — v1 stays v1 even after you ship v2. Embeds pin to a version on purpose, so a deploy doesn't silently change what production users see.
Two keys, on purpose. Your API key (mfk_...) stays on the server — create flows, read submissions, manage webhooks. Each flow gets an embed key (ifk_...) that's safe in the browser — submit intake, fetch the published schema. This split isn't ceremony. It's how you avoid leaking admin access into frontend bundles.
Server-side validation. Conditional fields, required-when rules, computed values — evaluated on the server before anything is saved. The embed SDK mirrors logic for UX, but the API is the source of truth. Someone can't skip a step with curl and get a free tier account anyway.
Webhooks that retry. On intake.submitted, we POST signed JSON to your endpoint. If your server is down or returns a 500, we retry with backoff (five attempts). There's a delivery log so you can see what landed and what didn't, instead of guessing from an empty inbox.
Idempotency. Pass Idempotency-Key on intake POST. Same key, same submission — no duplicate rows when mobile clients double-tap or your retry logic fires twice. Your webhook handler should dedupe on intake_id too, but the API gives you a head start.
Headless embed, no iframe. Drop in intake.js or call IntakeRelay.mount() in React. The form renders in your DOM with your CSS. No iframe sizing hacks, no postMessage bridge, no "why does this look nothing like our app."
Audit trail. Each submission gets an event log — validated, webhook queued, and so on. When support asks "did their signup go through?", you have an answer that isn't "check the logs and pray."
| Intake Relay | Typeform / Google Forms | Roll your own | |
|---|---|---|---|
| UI | Yours | Theirs | Yours |
| Schema versioning | Published, pinned versions | Change the form, everyone gets it | Whatever you remembered to document |
| Validation | Server-enforced | Mostly client-side | You build it |
| Webhooks | Signed, retried, logged | Varies | You build it |
| Duplicate submissions | Idempotency keys | Hope for the best | You build it |
| Best for | Intake inside your product | Surveys, lead gen | When you have time to maintain it forever |
We're not trying to replace a marketing team running NPS surveys. We're for the developer wiring onboarding into a dashboard — the flow that creates a tenant, opens a Stripe subscription, and pings Slack when someone finishes step three.
A few details that don't show up on a landing page but matter when you're debugging at 11pm:
?version=N on submit) means production traffic matches the schema you tested. No "we published a new field and half the embeds broke."429 quota_exceeded instead of a silent bill surprise.None of this is magic. It's the checklist you'd write yourself if you had another month. We just already wrote it.
Questions or weird edge cases: contact us. We built this because we needed it; happy to talk through whether it fits yours.