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)
  );
}

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