The endpoint
POST the field keys from your form as JSON. Values are coerced the way a browser would send them, so numbers can be strings and checkboxes can be "on". Hidden-by-rule fields are ignored. The response is explicit:
- 201 joined with position, or 200 already_joined without private entry details
- 409 waitlist_full and 423 waitlist_closed, both with reopensAt
- 422 validation_failed with a list of fieldKey, code, and message
- 429 rate_limited with Retry-After
Keys, captcha, and CORS
Browser calls send a Cloudflare Turnstile token. Server calls send a Bearer API key and skip the captcha. CORS is open for the two public endpoints and never uses credentials. A honeypot, a timing check, and per-IP limits stay on regardless.
Webhooks you can verify
Starter and Pro include signed webhooks. Eligible signup events send a JSON payload with an HMAC-SHA256 signature over a timestamp and the raw body. Failed deliveries retry, and you can inspect delivery logs or redeliver from the dashboard. Held Standard signups wait until released before side effects are dispatched.
Correct under concurrency
Positions and capacity are decided under a database row lock per waitlist. The integration test fires 50 concurrent joins at capacity 10 and gets exactly 10 entries numbered 1 to 10. Per-day, per-week, and per-month capacity use the same lock, with a time zone and reset time you choose.
Render it yourself
GET the form definition and theme as JSON and build a native form in React, Swift, or a terminal. Or point a plain HTML form at the endpoint and let it redirect back with the result. The hosted widget stays available for the pages where you do not want to write anything.

