Dev.to WebDev 🛠 Dev 👁 0 📖 4 min read

Getting Started with the GoHighLevel API: Auth, Webhooks and Common Gotchas

If a client runs on GoHighLevel (HighLevel, also sold as LeadConnector), someone will eventually ask you to "just sync it" with another system. The API makes that possible, but a few details catch almost everyone the fir

If a client runs on GoHighLevel (HighLevel, also sold as LeadConnector), someone will eventually ask you to "just sync it" with another system. The API makes that possible, but a few details catch almost everyone the first time. Here's what to know about auth, a first request, webhooks and the common gotchas.

Pick the right auth method first

HighLevel's current API (V2 and the newer v3 version) supports two ways to authenticate. The old V1 API keys reached end of support on December 31, 2025, so don't start anything new with them.

  • Private Integration Token (PIT). Best for your own scripts and internal tools that touch one agency or one sub-account. Create it under Settings > Private Integrations, choose only the scopes you need, and copy it once. If the menu is missing, HighLevel's docs say to check that the feature is enabled in Labs.
  • OAuth 2.0. Required when you're building a Marketplace app that many agencies or sub-accounts will install. Users approve scopes at install time, and you exchange the returned code for tokens.

Plan level matters too. Starter and Unlimited include basic, location-level access, while agency-level tokens and advanced OAuth features are tied to Agency Pro. Check this before designing anything agency-wide, such as creating sub-accounts.

Your first request

Calls go to https://services.leadconnectorhq.com, send JSON, and need two key headers: a bearer token and a Version. Here's a contact upsert with placeholders:

curl -X POST "https://services.leadconnectorhq.com/contacts/upsert" \
  -H "Authorization: Bearer <YOUR_PRIVATE_INTEGRATION_TOKEN>" \
  -H "Version: <API_VERSION_FROM_DOCS>" \
  -H "Content-Type: application/json" \
  -d '{
    "locationId": "<YOUR_LOCATION_ID>",
    "firstName": "Jane",
    "email": "[email protected]"
  }'

Upsert follows the sub-account's duplicate contact setting, matching on email or phone to decide whether to create or update. Test it against that setting before trusting it with real leads.

Gotcha 1: the Version header is not optional

HighLevel versions the API per request through the Version header. The docs list date-based values such as 2021-07-28 and 2023-02-21, plus the named v3 released on June 11, 2026. Each version has its own docs pages, so it's easy to read one reference while sending another. Pick a version in the docs switcher, keep it in config, and send it on every call.

Gotcha 2: OAuth tokens expire, and refresh tokens are used up

Access tokens last about 24 hours. The refresh token is valid for a year or until it's used, so every refresh returns a new refresh token you must save. Reuse the old one and the next refresh fails. Refresh on the server, store refresh tokens encrypted, and stop two workers from refreshing the same install at once.

Tokens come in two levels, Company (agency) and Location (sub-account). An agency token can request a location token through /oauth/locationToken, handy when one app manages many clients.

PITs don't expire, but HighLevel recommends rotating them every 90 days. During rotation the old and new tokens can both work for 7 days, so you can swap them without downtime.

Gotcha 3: verify webhooks against the raw body

Marketplace apps can subscribe to webhook events such as ContactCreate and AppointmentCreate. HighLevel signs each payload in the X-GHL-Signature header using Ed25519. The older RSA-based X-WH-Signature header was scheduled for deprecation on September 1, 2026, so verifiers built only for that header need updating.

The classic mistake is verifying parsed JSON. The signature covers the exact bytes sent, so use the raw body:

import express from "express";
import crypto from "node:crypto";

const app = express();
const GHL_PUBLIC_KEY = process.env.GHL_ED25519_PUBLIC_KEY; // PEM from HighLevel's webhook guide

app.post("/webhooks/ghl", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.get("x-ghl-signature");
  if (!signature) return res.sendStatus(401);

  const valid = crypto.verify(
    null,                              // Ed25519 takes no separate digest
    req.body,                          // raw Buffer, not parsed JSON
    GHL_PUBLIC_KEY,
    Buffer.from(signature, "base64")
  );
  if (!valid) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString("utf8"));
  // hand the event to a queue, then acknowledge quickly
  res.sendStatus(200);
});

Don't confuse these with workflow webhooks. The Inbound Webhook trigger and Custom Webhook action live inside HighLevel workflows and are premium, per-execution features: useful no-code glue, but a separate system.

Gotcha 4: rate limits are per app, per resource

For the public V2 APIs using OAuth, HighLevel lists a burst limit of 100 requests per 10 seconds and a daily limit of 200,000 requests. Both are counted per Marketplace app for each Location or Company, so every install gets its own budget. Responses include headers such as X-RateLimit-Remaining and X-RateLimit-Daily-Remaining. Back off before you hit a 429, especially during bulk imports.

Smaller things worth knowing

  • The calendar free-slots endpoint takes start and end dates as millisecond timestamps and can't span more than 31 days in one call.
  • HighLevel support doesn't debug API code, so lean on the official docs (marketplace.gohighlevel.com/docs) and the developer community.
  • Build against a test sub-account, keep tokens out of front-end code and Git, and strip personal data and tokens from logs.

Wrapping up

Pick the right token, pin your Version header, save every new refresh token, verify the raw webhook body and watch the rate limit headers, and most first-week bugs never happen. For plan-by-plan access details, an endpoint table and a comparison of the API with Zapier, workflow webhooks and MCP, see our longer GoHighLevel API guide.

Written by the team at AutogenCRM, a GoHighLevel setup and automation agency.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.