If you run a HighLevel integration — an n8n flow, a custom CRM bridge, a Make scenario, an internal Node service that listens for "Opportunity stage changed" — you have five weeks. On July 1, 2026, HighLevel deprecates the legacy X-WH-Signature (RSA) webhook signature header. After that date, every webhook is signed only with X-GHL-Signature using Ed25519.
From the HighLevel Webhook Integration Guide:
"The legacy header **X-WH-Signature* will be deprecated on July 1, 2026. After that date, webhooks will be signed only with X-GHL-Signature."*
The loud failure mode is the one everyone plans around. Your crypto.verify(...) call returns false, HighLevel sees a non-2xx from your endpoint, retries exhaust, the automation chain dies. You notice within an hour because the "new lead created" Slack notifier stops firing.
The failure modes worth grepping for now are the ones that don't surface as a 401. Three of them are common enough that they'll bite a meaningful chunk of the agency-built integrations running on HighLevel today.
What actually changes
Two things change simultaneously, and that's part of the trap:
| Surface | Before Jul 1, 2026 | After Jul 1, 2026 |
|---|---|---|
| Signature header |
X-WH-Signature (and X-GHL-Signature when present) |
X-GHL-Signature only |
| Algorithm | RSA (SHA-256) | Ed25519 |
| Public key | RSA public key endpoint | Ed25519 public key endpoint |
| Signature length | 256 bytes (2048-bit RSA) | 64 bytes |
| Verifier API (Node) | crypto.createVerify('SHA256').update(body).verify(pubkey, sig) |
crypto.verify(null, body, pubkey, sig) |
Two headers. Two algorithms. Two public keys. Two completely different verifier call shapes. A migration that's only a string replace from x-wh-signature to x-ghl-signature is silently broken on at least three of those rows.
1. The "skip if signature missing" branch becomes an open endpoint
This is the one to grep for first. Pattern:
app.post('/webhook/highlevel', (req, res) => {
const sig = req.headers['x-wh-signature'];
if (!sig) {
// dev/test environments don't sign — skip verification
return handlePayload(req.body, res);
}
if (!verifyRSA(req.body, sig, RSA_PUBLIC_KEY)) {
return res.status(401).send('bad sig');
}
handlePayload(req.body, res);
});
That if (!sig) early-return exists in roughly every webhook handler I've seen in agency-shipped HighLevel code. It's there because somebody wanted to test locally without setting up signature verification, or because an older version of HighLevel really did skip signing on certain event types.
After July 1, 2026, every production webhook from HighLevel arrives with X-WH-Signature absent. The handler reads req.headers['x-wh-signature'], gets undefined, takes the skip-verify branch, and processes the payload. Your endpoint is now unauthenticated. Anyone who knows the URL can POST arbitrary JSON to it — fake "stage changed to Won" events that trigger your billing automation, fake "new contact" events that pollute your CRM.
The fix is one line — switch the header name and remove the skip-verify branch — but the open-endpoint window between now and the fix is exactly the kind of thing that doesn't show up in logs because the requests succeed.
2. RSA verifier passed an Ed25519 signature returns false (silently)
The migration guide says to use X-GHL-Signature and provides Ed25519 verification snippets. What it doesn't emphasize is that you cannot mix and match the verifier call with the new header. Code that updates the header but keeps the RSA verifier looks like this:
const sig = req.headers['x-ghl-signature']; // updated
const verifier = crypto.createVerify('SHA256'); // not updated
verifier.update(JSON.stringify(req.body));
const ok = verifier.verify(RSA_PUBLIC_KEY, sig, 'base64');
// ok === false, because sig is a 64-byte Ed25519 sig
// and verifier is checking RSA-PSS / RSA-PKCS1
Two outcomes, both bad:
- If your handler returns 401 on
!ok, HighLevel retries with exponential backoff, gives up after the retry window, and the event is lost. No alert fires — HighLevel doesn't push failed-delivery dashboards into the agency dashboard, and most agency setups don't subscribe to delivery-failure events. - If your handler logs but still processes (an "alert on verify failure but proceed" pattern that some teams use during cutover), you're back to unauthenticated processing.
Either way, the symptom is the same the entire production stack from July 1 onward: deliveries look 200-green from HighLevel's side until you check the actual content delivered to your downstream system, and they look fine from your side until you realize the automations that should have fired never did.
A body-parser + RSA-verifier stack ported to Ed25519 also needs to handle the body differently — crypto.createVerify takes streamed updates; crypto.verify with null algorithm wants the full buffer. If your middleware already consumed the stream into a JSON object, you need the raw body preserved (bodyParser.raw or express.raw with a verify callback to stash req.rawBody).
3. Cached public keys work right up until the cutoff
A lot of agency setups load the HighLevel RSA public key into an environment variable at deploy time:
HIGHLEVEL_PUBKEY=-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0...
That key keeps working through June 30. On July 1, every signature is generated against a different keypair — the Ed25519 keypair. Your env var is now matched against signatures it has never seen and cannot validate by definition.
If you also took the time to switch verifiers (so issue #2 doesn't apply), you'll still fail because the public key is wrong. The verifier returns false. The signatures look "well-formed but invalid," which is indistinguishable in your logs from a real attack — and that's the worst place to be on July 1, because the on-call response to "all webhook signatures are suddenly invalid" is exactly the same as "we're being attacked": tighten the rate limit, page the security team, panic.
The fix is to fetch the Ed25519 public key from HighLevel's published JWKS-equivalent endpoint at runtime (with caching), not bake it into config. Even setting aside the July 1 cutoff, baking long-lived public keys into env vars is the pattern that makes every future key rotation a deploy event.
What to grep for this week
A 30-minute audit on every HighLevel webhook handler in your fleet:
-
grep -rn "x-wh-signature\|X-WH-Signature" .— every match is a code path that breaks on July 1. -
grep -rn "createVerify" .near webhook handlers — RSA verifier calls that need to becomecrypto.verify(null, ...)Ed25519 calls. -
grep -rn "if (!sig\|if (!signature\|sig === undefined" .— skip-verify branches that turn into open endpoints. - Search your env vars and secret stores for
HIGHLEVEL_PUBKEYor similar — anything pinned to the RSA key needs to be re-pointed at the Ed25519 key, ideally fetched at runtime. - Confirm your webhook handler preserves the raw body for Ed25519 verification —
body-parser's default JSON middleware consumes the stream and loses the byte-exact payload that the signature covers.
The cutoff is hard. HighLevel is not running a parallel-signed period where both headers ship — once X-WH-Signature is deprecated, it's gone. The thirty minutes spent grepping now is the difference between a quiet July 2 and explaining to a client why the "new lead → SMS notification" pipeline stopped firing five days into the month.
FlareCanary monitors API responses for schema drift, silent removals, and behavior changes across upstream providers. If you run integrations against HighLevel, Stripe, Shopify, GitHub, or anywhere else that ships breaking changes through changelog posts, flarecanary.com catches the drift before your customers do.
Top comments (0)