Setup

Connect Supabase and GoHighLevel

Live

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/ghl

Build 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=payment

The 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, and checkin / check in also 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"}}'