WhatsApp login (n8n webhook)
Participants are pre-registered from the Microsoft Forms CSV. They do not sign up with email anymore.
Flow
- User opens Login and enters their WhatsApp number (same as on the form).
- App checks
event_registrationsin Supabase. - App generates a 6-character code (example:
K7M2P4) and shows:- Message to send:
Hi Connext! Let me login to entripreneurship.fun (K7M2P4) - Button: Open WhatsApp (
https://wa.me/447441424421?text=...)
- Message to send:
- User sends that message to your AI WhatsApp bot number.
- WAHA (recommended) posts every message to
/api/auth/whatsapp/waha— seedocs/WHATSAPP_WAHA.md.
Or n8n calls/api/auth/whatsapp/webhookwith phone + code. - App marks the challenge confirmed and sends the finish link back on WhatsApp (WAHA) / browser polls.
- User lands in onboarding (PIN, team, etc.) like before.
Setup checklist
- Run SQL migration:
supabase/migrations/005_whatsapp_login.sqlin Supabase SQL Editor. - Import roster:
(usesnpx tsx scripts/import-registrations.tsdata/registrations.csv; requiresSUPABASE_SERVICE_ROLE_KEYin env) - Set environment variables (see
.env.example). - Configure n8n HTTP Request node (below).
- Deploy app and test one login end-to-end.
Environment variables
| Variable | Where | Purpose |
|---|---|---|
WHATSAPP_WEBHOOK_API_KEY |
Server only | Secret n8n sends on every webhook call |
WHATSAPP_BOT_NUMBER |
Server | Digits only, e.g. 447441424421 (no +; default Connext bot) |
NEXT_PUBLIC_WHATSAPP_BOT_NUMBER |
Optional client | Same number if you need it in UI later |
SUPABASE_SERVICE_ROLE_KEY |
Server | Create users + confirm logins |
NEXT_PUBLIC_APP_URL |
Server | Magic link redirect after login |
Generate a random API key, for example:
openssl rand -hex 32
Webhook endpoint
URL (production):
POST https://entripreneurship.fun/api/auth/whatsapp/webhook
URL (local):
POST http://localhost:3000/api/auth/whatsapp/webhook
Authentication
Send the same secret on every request (pick one):
- Header:
x-api-key: YOUR_WHATSAPP_WEBHOOK_API_KEY - Or header:
Authorization: Bearer YOUR_WHATSAPP_WEBHOOK_API_KEY
If the key is wrong or missing → 401 Unauthorized.
Request body (JSON)
Recommended (easiest for n8n):
{
"phone": "628978073890",
"code": "K7M2P4"
}
| Field | Required | Description |
|---|---|---|
phone |
Yes* | Sender WhatsApp in international form without + (62…) |
code |
Yes* | 6-character code from the app screen |
* Or send message instead and we parse the code:
{
"phone": "628978073890",
"message": "Hi Connext! Let me login to entripreneurship.fun (K7M2P4)"
}
Alternate field names (n8n can map your bot variables to these):
| Our field | Aliases accepted |
|---|---|
phone |
whatsapp, from, sender |
code |
otp |
message |
Full text; must include entripreneurship.fun (XXXXXX) (6-char code in parentheses) |
Example success response (200)
{
"ok": true,
"challengeId": "uuid-of-challenge",
"userId": "uuid-of-supabase-user",
"finishUrl": "https://entripreneurship.fun/api/auth/whatsapp/finish?challengeId=uuid-of-challenge",
"hint": "Send finishUrl to the user on WhatsApp so they can open the app in one tap."
}
Example error responses
| Status | Body | Meaning |
|---|---|---|
401 |
{ "error": "Unauthorized" } |
Wrong API key |
400 |
{ "ok": false, "error": "invalid_payload" } |
Missing phone/code |
404 |
{ "ok": false, "error": "invalid_or_expired" } |
Wrong code, wrong phone, or expired (>10 min) |
503 |
Webhook key not set on server | Fix server env |
n8n workflow (sketch)
- Trigger — WhatsApp message received (your bot / Evolution / Meta provider).
- IF — Message contains
entripreneurship.fun(optional). - Set — Map fields:
phone→ sender number (digits only; Indonesian08…→62…if needed).code→ 6 characters inside(...)at end of message, or pass fullmessage.
- HTTP Request
- Method:
POST - URL:
https://entripreneurship.fun/api/auth/whatsapp/webhook - Headers:
x-api-key={{ $env.WHATSAPP_WEBHOOK_API_KEY }} - Body: JSON as above
- Method:
- WhatsApp reply (recommended) — On
200, the webhook returnsfinishUrl. Send it to the user:- “You’re verified. Tap to open the app: {{ $json.finishUrl }}”
- This works when the browser tab was in the background (common on mobile).
- Optional — User can also return to Chrome and tap I sent the message — check now.
Phone normalization tip
Indonesian numbers often arrive as 0897… or 897…. The app stores 628…. In n8n:
- Remove non-digits
- If starts with
0, replace with62 - If starts with
8(no country code), prefix62
Other API routes (for reference)
| Method | Path | Who calls | Purpose |
|---|---|---|---|
POST |
/api/auth/whatsapp/start |
Browser | Start login, get code + waUrl |
GET |
/api/auth/whatsapp/status?challengeId= |
Browser poll | pending / confirmed / expired |
POST |
/api/auth/whatsapp/webhook |
n8n | Confirm OTP |
GET |
/api/auth/whatsapp/finish?challengeId= |
Browser redirect | Supabase session (magic link) |
Security notes
- Keep
WHATSAPP_WEBHOOK_API_KEYonly on the server and in n8n credentials — never in the browser. - Codes expire after 10 minutes.
- Only one pending code per phone at a time; starting again invalidates the old one.
- Roster table has no public RLS; only the service role (API routes) can read/write it.
Troubleshooting
| Problem | Fix |
|---|---|
| “Not on registration list” | Re-run import script; check phone matches form |
| Webhook 404 | Code typo, expired, or phone mismatch vs browser (webhook also matches by code alone as fallback) |
| Browser stuck on “Waiting…” | Mobile tab throttled — user taps Check now or open finishUrl from WhatsApp |
| Magic link fails | Add https://entripreneurship.fun/** to Supabase → Auth → URL configuration → Redirect URLs |
| WhatsApp button missing | Set WHATSAPP_BOT_NUMBER |
| Login loop after confirm | Check NEXT_PUBLIC_APP_URL matches live domain |
| n8n 401 | Match x-api-key to server WHATSAPP_WEBHOOK_API_KEY |