Skip to content

Webhooks

Webhooks post a JSON event to a URL you choose whenever something happens in your assistant. They’re available on the Startup plan and above, and work with any tool that accepts an incoming webhook:

Two events are available. Each webhook subscribes to one; add the same URL twice to receive both.

Event Sent when
new_lead A visitor submits the lead form or leaves their email for a human handoff
escalation_requested A visitor asks for a person and leaves their email. Includes the assistant’s summary
booking_created A visitor books through Cal.com in the chat. Includes booking with uid, start (UTC, the first time), starts (every time booked, for multi-day bookings), event_type, notes and paid (true when paid via a Cal.com payment link)

Handoffs and bookings also count as leads, so they send new_lead too. If you use Freshdesk, handoffs can also open a ticket directly, without a webhook. Its new_lead payload has "source": "escalate_to_human" in data, so you can filter it out if you subscribe to both.

  1. Open your chatbot and go to Webhooks.
  2. Paste a public https URL, pick the event, and click Add webhook.
  3. Click Send test to post a sample event, then check that it arrived.

Each webhook gets its own signing secret, shown on its card. You can rotate it at any time.

{
"id": "evt_4f0c9a2e7b1d4c3a9e8f6b5a4d3c2b1a",
"event": "new_lead",
"chatbotId": "k57abc123...",
"leadId": "j97def456...",
"conversationId": "conv_8f2d...",
"data": {
"name": "Maya Chen",
"email": "maya@example.com",
"phone": "+1 555 0100"
},
"tags": {
"intent": "booking",
"intent_confidence": 0.92,
"urgency": 0.67,
"urgency_label": "Wants it within the next few days",
"qualified": 0.88
},
"timestamp": 1760281200000
}
  • data contains whichever fields the visitor filled in, including answers to any extra questions on your lead form.

  • tags is PageClerk’s read of the conversation, when available:

    • intent: one of booking, quote, purchase, support, complaint, question, other, with intent_confidence (0–1)
    • urgency: 0 (no time pressure) to 1 (today or an emergency), with a readable urgency_label
    • qualified: 0–1, how likely this is a real prospect worth following up

    Use them to route in your automation, for example complaints to a manager or qualified above 0.7 to a sales pipeline. If tagging isn’t available, tags is left out and the event is still sent.

  • escalation_requested events add summary: the assistant’s description of what the visitor needs (empty if none was produced).

  • timestamp is in milliseconds.

  • Test events include "test": true and sample data.

Every request also carries these headers:

Header Value
X-PageClerk-Event The event name, e.g. new_lead
X-PageClerk-Delivery The event id. Use it to drop duplicates
X-PageClerk-Signature t=<unix seconds>,v1=<hex>

In Settings → Lead capture → Extra questions, add questions such as “What do you need?” with follow-ups that only appear for some answers. Each answer is sent in data under the question’s key, for example "project_type": "A full project".

Return any 2xx status to acknowledge the event. Network errors, timeouts (10 seconds), 408, 429, and 5xx responses are retried after 1 minute and again after 10 minutes. Other 4xx responses aren’t retried. The webhook card shows the result of the last delivery.

Because an event can arrive more than once, use X-PageClerk-Delivery to ignore repeats.

The signature is an HMAC-SHA256 of <t>.<raw request body>, keyed with your signing secret and hex-encoded. Compute it over the raw body exactly as received, before parsing the JSON.

import crypto from "node:crypto";
export function verifyPageClerk(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return (
expected.length === parts.v1?.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
);
}

Checking the timestamp blocks replays of old requests.

  1. Add a Webhook trigger node. Set the method to POST and copy its Production URL.
  2. Paste the URL into PageClerk and click Send test. Use the Test URL instead while the workflow is in “Listen for test event” mode.
  3. Map fields from body.data (for example {{ $json.body.data.email }}) into the next node, such as Google Sheets, HubSpot, or Slack.

To verify the signature in n8n, add a Crypto node (HMAC, SHA256, hex) over {{ $json.headers["x-pageclerk-signature"].split(",")[0].slice(2) }}.{{ JSON.stringify($json.body) }} and compare it with the v1 value. n8n re-serializes the body, so for strict verification turn on Raw Body in the Webhook node’s options and sign that instead.

  1. Create a Zap with Webhooks by Zapier → Catch Hook as the trigger and copy the hook URL.
  2. Paste it into PageClerk and click Send test.
  3. In Zapier, click Test trigger. The sample lead appears with data__email, data__name, and the other fields ready to map.
  1. Create a scenario starting with Webhooks → Custom webhook and copy the address.
  2. Paste it into PageClerk, click Run once in Make, then click Send test so Make learns the data structure.

Send leads straight into your HighLevel contacts and pipelines, in your own account or a client’s sub-account.

  1. In the sub-account, go to Automation → Workflows and create a workflow from scratch.
  2. Add the Inbound Webhook trigger and copy its URL.
  3. Paste the URL into PageClerk and click Send test. Back in HighLevel, click Fetch Sample Request on the trigger, select the test event, and save the trigger.
  4. Add a Create Contact action. For each field, open the custom values menu, choose Inbound Webhook Trigger, and pick the matching value: data → name, email and phone.
  5. Optionally add Create/Update Opportunity to place the lead in a pipeline stage, Add Tag (for example pageclerk, or the tags → intent value), and Internal Notification to alert your team. Use an If/Else on the tags → qualified or intent value to route qualified leads and complaints differently. For an escalation_requested webhook, add the summary value to a note or notification so your team sees what the visitor needs.
  6. Save and publish the workflow.

Inbound Webhook is a premium workflow trigger, so HighLevel may bill each run depending on your plan. HighLevel workflows can’t verify the signature header, so keep the webhook URL private. If it leaks, add a new Inbound Webhook trigger and replace the URL in PageClerk.