Status
What this deployment can currently do
Supabase connection — configured
Needs NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY. Without these the dashboard shows sample data.
Webhook secret — configured
Needs GHL_WEBHOOK_SECRET. The webhook endpoint rejects every request until this is set.
1 · Create the tables
Supabase → SQL Editor → New query
Run npx supabase db push from the repo, or paste the migration in supabase/migrations/ into the SQL editor. It creates members, member_events, webhook_events and coaches, and turns on row level security with no public policies — the app reaches the data with the service-role key from server code only.
2 · Point GoHighLevel at this URL
Workflow → Add action → Webhook
Method POST, with a custom header x-webhook-secret set to your GHL_WEBHOOK_SECRET.
While Vercel Deployment Protection is on, add a second header x-vercel-protection-bypass set to your automation bypass secret (Project → Settings → Deployment Protection). Without it Vercel answers every webhook with a redirect to an SSO login and GoHighLevel records a delivery that never arrives.
https://demo.dadson.digital/api/webhooks/ghlBuild two workflows. The notice one fires when a member tells you they are leaving; the churn one fires when the membership actually ends. If it is easier than adding a field, put the event in the URL instead:
https://demo.dadson.digital/api/webhooks/ghl?event=notice
https://demo.dadson.digital/api/webhooks/ghl?event=churn
https://demo.dadson.digital/api/webhooks/ghl?event=paymentThe payment workflow drives the billing-vs-notice analysis. Fire it on successful charges only; failed, declined and refunded events are ignored by design.
3 · What to send
Every field is optional except the contact id
{
"contact_id": "{{contact.id}}",
"first_name": "{{contact.first_name}}",
"last_name": "{{contact.last_name}}",
"email": "{{contact.email}}",
"tags": "{{contact.tags}}",
"customData": {
"event_type": "notice", // or "churn" / "rejoin" / "payment"
"notice_date": "2026-09-11", // omit and it defaults to today
"end_date": "2026-10-11", // when the membership actually stops
"payment_date": "2026-09-01", // for payment events
"amount": "45.00" // optional
}
}- Tags can be an array or a comma-separated string — both work.
- Coach is read from a tag shaped like
(09) jen check-in. The number is optional, andcheckin/check inalso match. - Replays are safe. The same contact, event and date will not be counted twice.
- A future end date is treated as scheduled, not churned. The member flips to churned on their own when that date passes — no second webhook needed.
4 · Test it
From your terminal
curl -X POST 'https://demo.dadson.digital/api/webhooks/ghl?event=notice' \
-H 'content-type: application/json' \
-H 'x-webhook-secret: YOUR_SECRET' \
-d '{"contact_id":"test-001","first_name":"Test","last_name":"Member",
"tags":"(09) jen check-in, member",
"customData":{"notice_date":"2026-09-11","end_date":"2026-10-11"}}'