Back to home

API Documentation

Everything you need to integrate WaitStack into your application.

Quick Start

Get your waitlist running in 3 steps:

  1. Create a project in your dashboard
  2. Generate an API key
  3. Make a POST request to add users to your waitlist
curl -X POST https://your-app.com/api/public/v1/join \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com"}'

Authentication

All API requests require a Bearer token in the Authorization header:

Authorization: Bearer YOUR_API_KEY

API keys can be created in your project's API Keys section. Keep your keys secure and never expose them in client-side code.

Join Endpoint

Add a user to your waitlist.

POST/api/public/v1/join

Request Body

{
  "email": "user@example.com",
  "referral_code": "abc123"  // optional - credits the referrer
}

Response

{
  "id": "uuid",
  "email": "user@example.com",
  "referral_code": "xyz789",    // user's unique referral code
  "rank_position": 42,          // position in waitlist
  "referral_link": "https://your-app.com/join?ref=xyz789"
}

Error Responses

  • 400 - Invalid email or already registered
  • 401 - Invalid or missing API key
  • 429 - Rate limit exceeded

Status Endpoint

Check a user's position and referral stats.

GET/api/public/v1/status/[referralCode]

Response

{
  "rank_position": 42,
  "total_participants": 1234,
  "referral_count": 5,
  "email_verified": true
}

Verify Endpoint

Verify a user's email address using the token sent to them.

POST/api/public/v1/verify

Request Body

{
  "token": "verification_token_from_email"
}

Response

{
  "success": true,
  "rank_position": 42
}

Webhooks

Receive real-time notifications when events occur on your waitlist. (Launch and Scale tiers)

Events

participant.created

Triggered when someone joins your waitlist.

participant.verified

Triggered when someone verifies their email.

participant.referred

Triggered when someone joins via a referral.

Webhook Payload

{
  "event": "participant.created",
  "timestamp": "2024-01-15T12:00:00Z",
  "data": {
    "id": "uuid",
    "email": "user@example.com",
    "referral_code": "xyz789",
    "rank_position": 42
  }
}

Verifying Signatures

All webhooks include a signature header for verification:

X-WaitStack-Signature: sha256=abc123...

// Verify in Node.js:
const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Squad Mode

Squad Mode lets users form teams of 3 to earn bonus points. Enable it in your project settings to encourage viral growth.

How It Works

  1. A verified user creates a squad and gets a unique code (e.g., SQUAD-ABC123)
  2. They share the code or squad link with friends
  3. When 3 members join and all verify their emails, everyone gets bonus points

Squad API

POST/api/public/v1/squad

Create Squad

{
  "action": "create",
  "participant_id": "uuid",
  "project_id": "uuid"
}

Join Squad

{
  "action": "join",
  "participant_id": "uuid",
  "squad_code": "SQUAD-ABC123"
}

Response (Create)

{
  "squad_id": "uuid",
  "code": "SQUAD-ABC123",
  "message": "Squad created! Share this code with friends."
}
GET/api/public/v1/squad?code=SQUAD-ABC123

Get Squad Info

{
  "code": "SQUAD-ABC123",
  "is_complete": false,
  "bonus_applied": false,
  "members": [
    { "id": "uuid", "email": "j***@example.com", "verified": true },
    { "id": "uuid", "email": "s***@example.com", "verified": false }
  ],
  "spots_remaining": 1
}
DELETE/api/public/v1/squad

Leave Squad

{
  "participant_id": "uuid"
}

Users cannot leave after the squad bonus has been applied.

Rules

  • Maximum 3 members per squad
  • Maximum 2 members from the same email domain
  • All members must verify their email for bonus to apply
  • Users can only be in one squad at a time
  • Configure bonus points in project settings (default: 500 pts)

Gamification API

Engage your waitlist with milestones, prizes, leaderboards, and more. (Launch and Scale tiers)

Available Endpoints

GET /api/public/v1/milestones/[projectId]

Get referral milestones and rewards for a project.

GET /api/public/v1/prizes/[projectId]

Get available prizes and drawing entries.

GET /api/public/v1/leaderboard/[projectId]

Get the top referrers leaderboard.

POST /api/public/v1/spin/[projectId]

Spin the wheel for a chance to win prizes.

GET /api/public/v1/badges/[projectId]

Get badge definitions and user progress.

Configure gamification features in your project's Gamification dashboard section.

Pre-orders API

Accept payments from your waitlist with position-based pricing. (Launch and Scale tiers)

Available Endpoints

POST /api/public/v1/preorders

Create a pre-order for a waitlist member. Returns a Stripe checkout URL.

GET /api/public/v1/preorders?participant_id=...

Get pre-order status for a participant.

Position-Based Pricing

Pre-orders support tiered pricing based on waitlist position. Early users get better discounts:

// Example tier configuration
{
  "tiers": [
    { "position_from": 1, "position_to": 100, "discount_percent": 40 },
    { "position_from": 101, "position_to": 500, "discount_percent": 25 },
    { "position_from": 501, "position_to": 1000, "discount_percent": 15 },
    { "position_from": 1001, "position_to": null, "discount_percent": 0 }
  ]
}

Stripe Connect

Pre-orders use Stripe Connect for payments. Set up your seller account in the Pre-orders dashboard section to receive payouts.

Code Examples

Preview

Join 1,234 others on the waitlist

Success State

You're #42 on the waitlist!

Share to move up the queue

Code

"use client";

import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { CheckCircle, Copy, Loader2 } from "lucide-react";
import { toast } from "sonner";

const API_KEY = process.env.NEXT_PUBLIC_WAITSTACK_KEY!;

interface WaitlistResult {
  referral_code: string;
  rank_position: number;
  referral_link: string;
}

export function WaitlistForm() {
  const [email, setEmail] = useState("");
  const [status, setStatus] = useState<"idle" | "loading" | "success">("idle");
  const [result, setResult] = useState<WaitlistResult | null>(null);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setStatus("loading");

    try {
      const res = await fetch("/api/public/v1/join", {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${API_KEY}`,
          "Content-Type": "application/json"
        },
        body: JSON.stringify({ email })
      });

      if (!res.ok) throw new Error("Failed to join");

      const data = await res.json();
      setResult(data);
      setStatus("success");
    } catch {
      toast.error("Something went wrong");
      setStatus("idle");
    }
  }

  if (status === "success" && result) {
    return (
      <Card>
        <CardContent className="pt-6 text-center space-y-4">
          <CheckCircle className="h-12 w-12 text-green-500 mx-auto" />
          <div>
            <h3 className="text-xl font-semibold">
              You're #{result.rank_position}!
            </h3>
            <p className="text-muted-foreground">
              Share to move up the queue
            </p>
          </div>
          <div className="flex gap-2">
            <Input value={result.referral_link} readOnly />
            <Button
              variant="outline"
              onClick={() => {
                navigator.clipboard.writeText(result.referral_link);
                toast.success("Copied!");
              }}
            >
              <Copy className="h-4 w-4" />
            </Button>
          </div>
        </CardContent>
      </Card>
    );
  }

  return (
    <Card>
      <CardHeader>
        <CardTitle>Join the Waitlist</CardTitle>
      </CardHeader>
      <CardContent>
        <form onSubmit={handleSubmit} className="space-y-4">
          <div className="space-y-2">
            <Label htmlFor="email">Email</Label>
            <Input
              id="email"
              type="email"
              placeholder="you@example.com"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              required
            />
          </div>
          <Button type="submit" className="w-full" disabled={status === "loading"}>
            {status === "loading" ? (
              <>
                <Loader2 className="h-4 w-4 mr-2 animate-spin" />
                Joining...
              </>
            ) : (
              "Join Waitlist"
            )}
          </Button>
        </form>
      </CardContent>
    </Card>
  );
}

Need help?

Have questions or need assistance? Email us at support@waitstack.co