How to call the GoHighLevel API from a Supabase Edge Function

    A Deno Edge Function that authenticates to GHL, checks calendar availability, and books — with secrets, signature verification, CORS, and the deploy pattern this site's own booking runs on.

    Last updated 2026-09-03~6 min read
    SupabaseDenoGoHighLevelEdge FunctionsCalendar API

    Skip the scaffold. Download the starter Edge Function — a Deno handler with GHL auth from secrets, the free-slots and book-appointment calls, CORS + OPTIONS handling, and a signature-verification stub. Drop it into supabase/functions and deploy.

    When to use an Edge Function instead of n8n

    n8n is the right tool when a non-developer will maintain the automation, or when you want a visual audit trail of every step. A Supabase Edge Function — Deno running at the edge — is the right tool when the glue lives inside a Supabase app you already own: you want it version-controlled with the rest of your code, deployed with supabase functions deploy, and running with sub-second cold starts next to your database. GHL webhooks in, GHL API calls out, no separate automation platform to keep alive.

    This site's own booking runs on exactly this pattern: get-availability and book-appointment Edge Functions call the GoHighLevel calendar API and are what the booking widget (and the Vapi voice agent) hit. What follows is that pattern, generalized.

    Prerequisites

    • A Supabase project and the Supabase CLI installed (supabase login done)
    • A GHL Private Integration token scoped to whatever the function does
    • Deno basics — the function is a single Deno.serve handler

    Step 1: Scaffold the function

    supabase functions new ghl-availability

    That creates supabase/functions/ghl-availability/index.ts with a Deno.serve handler. Everything below goes in that file.

    Step 2: Authenticate to GHL from Deno — secrets, not code

    Never paste the token into the source. Set it as a Supabase secret and read it from the environment at runtime:

    supabase secrets set GHL_TOKEN=pit-xxxx... GHL_LOCATION_ID=abc123
    
    // index.ts
    const token = Deno.env.get("GHL_TOKEN")!;
    const headers = {
      "Authorization": `Bearer ${token}`,
      "Version": "2021-07-28",
      "Content-Type": "application/json",
    };

    The Version header is mandatory on the GHL v2 API — a valid token without it still returns 401. If you hit that, the 401 fix applies here too; the auth rules are identical whether the caller is n8n or Deno.

    Step 3: The availability / booking handler

    Parse the request, call the GHL calendar API, return JSON. Keep it to one GHL call per invocation — an Edge Function that fans out to five endpoints is slow and hard to debug.

    Deno.serve(async (req) => {
      const { startDate, endDate, timezone } = await req.json();
      const cal = Deno.env.get("GHL_CALENDAR_ID")!;
      const url = new URL(
        `https://services.leadconnectorhq.com/calendars/${cal}/free-slots`);
      url.searchParams.set("startDate", startDate);
      url.searchParams.set("endDate", endDate);
      url.searchParams.set("timezone", timezone);
    
      const res = await fetch(url, { headers });
      const data = await res.json();
      return new Response(JSON.stringify(data), {
        headers: { "Content-Type": "application/json", ...cors },
      });
    });

    Step 4: Verify inbound webhook signatures

    If the function receives a GHL webhook (rather than being called by your own app), verify it's really from GHL before acting — otherwise anyone with the URL can POST fake events. Check the shared secret or signature header GHL sends, and reject on mismatch. Same principle as any webhook receiver: trust nothing until it's verified.

    Step 5: Deploy and (optionally) schedule

    supabase functions deploy ghl-availability
    # public URL:
    # https://<project-ref>.supabase.co/functions/v1/ghl-availability

    For recurring jobs (nightly sync, pipeline hygiene), trigger the function on a schedule with pg_cron + pg_net from inside Postgres, so the whole thing stays within Supabase.

    Common gotchas

    1. CORS. If a browser calls the function (a booking widget), you must return CORS headers and handle the OPTIONS preflight explicitly — Deno doesn't add them for you. Missing CORS is the #1 "works in curl, fails in the browser" cause.

    2. Cold starts. Edge Functions cold-start fast but not instantly. On a latency-sensitive path (a live voice call), keep the function warm or accept the first-call delay — see the latency notes in the Vapi voice agent guide.

    3. Secret rotation. When you rotate the GHL token, supabase secrets set the new value and redeploy — running functions read secrets at boot, so a rotation without redeploy leaves the old token in memory until the next cold start.

    4. The service-role key. If the function writes to your database, it uses the service-role key, which bypasses RLS. Scope what the function does carefully — see Supabase RLS patterns for multi-tenant agencies.

    Browse 4,900+ ready-to-import workflow templates →

    Need this workflow customized or debugged? Book a 30-min scope call →

    Stuck configuring this workflow?

    I build and fix n8n workflows for agencies and SaaS teams. 30-minute scope call — bring the problem, leave with an estimate.

    Related pages

    Related workflows