Signup Form
How the public signup form works and what happens when someone joins.
How Signups Work
When someone submits the signup form, the following happens:
- Waitlist status check — if the waitlist is closed, the signup is rejected with a
403error - Bot check — a hidden honeypot field catches automated submissions (bots get a fake success response and no further processing occurs)
- Consent check — if the waitlist requires consent, the
consentfield must be truthy or the signup is rejected with400 VALIDATION_ERROR(with a consent field error in theerrorsarray) - Validation — email format, required fields, domain restrictions, and custom question answers are checked
- Duplicate check — if the email already exists on this waitlist, the existing signup data is returned (no duplicate created)
- Atomic creation — a database transaction atomically increments the signup counter and creates the signup record (including consent records if applicable)
- Referral credit — if a
referred_by_tokenwas included, the referrer’s count is incremented and their position is boosted by the configured referral boost amount - Referral notification — if enabled, the referrer receives an email about the new signup (rate-limited to 1/hour)
- Analytics — daily signup counters and UTM counters are updated in the background
- Notifications — webhooks, Slack, and Discord notifications fire in the background
The signup receives:
- Their position on the waitlist
- A unique referral token they can share to move up
- The total signup count
Hosted Signup Page
Every waitlist has a hosted page at:
https://makeemwait.com/waitlist.html?id=YOUR_WAITLIST_ID
The page automatically loads the waitlist configuration and renders the form with your custom headline, subheadline, hero image, and questions.
Referral Links
To pre-fill a referral, append the ref parameter:
https://makeemwait.com/waitlist.html?id=YOUR_ID&ref=REFERRAL_TOKEN
When someone signs up through a referral link, the referrer automatically gets credit.
UTM Tracking
The signup form automatically captures UTM parameters from the page URL:
?utm_source=twitter&utm_medium=social&utm_campaign=launch&utm_term=waitlist&utm_content=hero
The supported UTM parameters are utm_source, utm_medium, utm_campaign, utm_term, and utm_content. These are stored with the signup and available in CSV exports and the API. Pro users can view UTM breakdowns in Analytics.
Each UTM field is sanitized before storage: values are truncated to a maximum of 200 characters and any control characters (non-printable characters like null bytes, tabs, or newlines) are stripped. This keeps data clean in exports.
Automatic Data Capture
In addition to the fields the user fills in, the signup form automatically captures two pieces of contextual data with every submission:
Device type — The API classifies the visitor’s device based on their User-Agent header. The device_type field is stored with the signup record and will be one of:
| Value | Detected When |
|---|---|
desktop |
Default — standard browsers on laptops and desktops |
mobile |
iPhones, Android phones, Opera Mini, Windows Phone, BlackBerry |
tablet |
iPads, Android tablets, Kindle, Silk browser |
unknown |
No User-Agent header present |
Timezone — The visitor’s timezone is captured client-side using Intl.DateTimeFormat().resolvedOptions().timeZone (e.g., "America/New_York", "Europe/London") and sent in the timezone field of the signup request. This happens automatically in the hosted signup page, the embeddable widget, and the plain embed. The timezone is stored with the signup record and is visible in Pro analytics charts.
Validation
The signup endpoint validates all incoming data before creating a record. The following limits apply:
| Field | Max Length | Notes |
|---|---|---|
email |
320 characters | Must be a valid email format |
first_name |
100 characters | Required when waitlist has name_field: "required" |
last_name |
100 characters | Required when waitlist has name_field: "required" |
phone |
20 characters | Required when waitlist has phone_field: "required" |
custom_answers |
1,000 characters per answer | Must match question type (dropdown answers must be a valid option) |
| UTM fields | 200 characters each | Control characters are stripped; 5 fields max |
Required custom questions must have a non-empty answer. Dropdown-type custom questions must have an answer that matches one of the predefined options.
Duplicate Signup Behavior
If someone submits the signup form with an email that already exists on the waitlist, the API does not return an error. Instead, it returns the existing signup data with an HTTP 200 status code (rather than the 201 used for new signups). No duplicate record is created, no analytics are incremented, and no webhooks fire.
This makes the signup endpoint idempotent — submitting the same email multiple times is safe and always returns the same result. This is useful when:
- A user accidentally submits the form twice
- The form is re-submitted after a network timeout
- You’re building a “check my status” flow that reuses the signup endpoint
Bot Protection
Every signup form includes a hidden website field (honeypot). It’s invisible to real users but gets filled in by bots. When the honeypot is triggered:
- The bot receives a fake
201response with fabricated data (a fake referral token, position 1, referral count 0, and the current timestamp) - No actual signup record is created
- No analytics or webhooks fire
- No validation is performed on the other fields
The fake response is indistinguishable from a real success response, so bots have no way to know they were caught. This happens transparently — no CAPTCHAs or friction for real users.
For additional bot protection, you can also enable reCAPTCHA v3 — an invisible scoring system that detects sophisticated bots without any user interaction. See reCAPTCHA v3 Bot Protection in the Security docs for setup details.
Domain Restrictions (Advanced+)
You can restrict who can sign up by email domain:
- Allowed email domains — only accept signups from specific domains (e.g.,
company.com,university.edu) - Exclude personal emails — block free email providers like Gmail, Yahoo, Hotmail, etc.
These are configured in your waitlist settings.
Custom Redirect URL (Advanced+)
Instead of showing the default success page after signup, you can redirect signups to your own URL — for example, a thank-you page, onboarding flow, or external site.
Set the Redirect URL in your waitlist settings. The URL must be HTTPS and no longer than 500 characters.
When a redirect URL is set, the following query parameters are appended automatically:
| Parameter | Description |
|---|---|
position |
The signup’s position on the waitlist |
referral_token |
Their unique referral token for sharing |
total_signups |
Total number of signups on the waitlist |
Example redirect:
https://yoursite.com/thank-you?position=42&referral_token=f7a3b2c1&total_signups=500
This works on both the hosted signup page and the embeddable widget. When no redirect URL is set, the default behavior is used (success page for hosted, inline success message for widget).
Email Verification (Pro)
When require_email_verification is enabled on a waitlist, signups receive a verification email with a clickable link after joining. Clicking the link verifies their address automatically.
How It Works
- When a signup is created, a secure verification token (64 hex characters from 32 random bytes) is generated and stored with the signup record
- A verification email is sent in the background with a link to
verify.htmlcontaining the waitlist ID, email, and token as query parameters - When the user clicks the link, the verify page calls the API to confirm the token
- On success, the
email_verifiedfield is set totrueand theverification_tokenis removed from the record - If the email was already verified, the API returns success with the message “Email already verified”
Verification Fields
The signup record tracks:
email_verified— whether the email has been confirmed (trueor absent)verification_token— a unique token embedded in the verification link (removed after successful verification)
API Endpoints
Both POST and GET are supported:
GET /public/waitlists/{id}/signups/{email}/verify?token=TOKEN
POST /public/waitlists/{id}/signups/{email}/verify
{ "token": "..." }
The GET method is used by the clickable link in the verification email. Both methods return the same JSON response on success:
{
"verified": true,
"message": "Email verified successfully"
}
The token comparison uses timing-safe comparison (crypto.timingSafeEqual) to prevent token guessing via timing side-channel attacks.
Consent Collection
When require_consent is enabled on a waitlist, the signup form displays an unchecked consent checkbox. Users must check it before they can submit the form.
The signup request must include "consent": true or it will be rejected with 400 Bad Request and the error code VALIDATION_ERROR (with a consent field error in the errors array). When consent is given, the following are stored with the signup record:
| Field | Description |
|---|---|
consent_given_at |
ISO 8601 timestamp of when consent was given |
consent_text_version |
Version identifier of the consent text shown ("custom" if custom text was set, "v1" for the default) |
privacy_policy_version |
Version of the privacy policy at the time of consent ("v1") |
These fields are included in CSV exports. See Creating Waitlists for configuration details.
Closed Waitlist
When a waitlist’s status is set to "closed":
- The hosted signup page shows “Waitlist Closed” with the message “This waitlist is no longer accepting signups”
- The embed widget shows a “This waitlist is currently closed” message instead of the form
- The API rejects new signups with
403 Forbiddenand the message “This waitlist is currently closed”
You can close and reopen a waitlist at any time from the dashboard or via the API.
Position Offset (Advanced+)
When a position_offset is configured, all position numbers displayed to signups are inflated by that amount. For example, with an offset of 500:
- The 1st signup sees position 501
- The 42nd signup sees position 542
- Total signups displayed is also inflated
This creates the impression of a larger, more established waitlist. Internal positions used for referral ranking are not affected.
Success Page
After signing up, users are redirected to /success.html which shows:
- Their position on the waitlist (unless hidden by Advanced+ settings)
- Their unique referral link with a copy button
- Social sharing buttons (X/Twitter, Facebook, WhatsApp, Instagram)
- The referral leaderboard showing top referrers
- Email verification status (if enabled by Pro users — a verification link is sent by email)
The success page reads its data from URL query parameters:
| Parameter | Description |
|---|---|
wl |
Waitlist ID |
email |
The signup’s email address |
pos |
Position on the waitlist |
ref |
The signup’s referral token |
total |
Total number of signups on the waitlist |
If the waitlist owner has configured a custom redirect URL, the user is redirected there instead of /success.html, with the same query parameters appended.
Custom Questions
Custom questions appear on the signup form below the standard fields. Users’ answers are stored with their signup record and included in:
- CSV exports
- API responses when listing signups
- Webhook payloads
Question Types
| Type | Behavior |
|---|---|
| Text | Free-form text input |
| Dropdown | Select one option from a predefined list |
| Checkbox | Yes/no toggle |
Validation
- Required questions must be answered before the form submits
- Dropdown answers must match one of the predefined options
- Text answers are truncated to 1,000 characters
Signup Count
The public count endpoint returns the total number of signups for a waitlist:
GET /public/waitlists/{id}/count
The count returns null when:
- The waitlist has 10 or fewer signups (to avoid showing low numbers)
- The waitlist owner has Hide position enabled
Related
- Creating Waitlists — configure fields, custom questions, consent, and all waitlist settings
- Embed Widget — add a signup form to any website with one line of code
- Referral System — how viral referral tracking and leaderboards work
- Error Handling — error codes and response formats for all API endpoints