Why Intake Relay exists

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.


What you actually get

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."


How this compares to what you might reach for instead

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.


The stuff that saves you later

A few details that don't show up on a landing page but matter when you're debugging at 11pm:

None of this is magic. It's the checklist you'd write yourself if you had another month. We just already wrote it.


When it's probably not for you


Where to go from here

  1. Create a free account — 10 submissions/month, one flow, enough to prove it works in your stack.
  2. Make your first onboarding form — ~10 minutes, curl only, no frontend required.
  3. Getting started — API base URL, auth headers, plan limits.
  4. Embedding — drop the SDK into your app when you're ready for real UI.

Questions or weird edge cases: contact us. We built this because we needed it; happy to talk through whether it fits yours.