Send events to other apps with webhooks

Send form submissions, leads, client uploads, published posts, Zeus scans, reviews, citation and social updates and more to Zapier, Make, n8n, GoHighLevel or your own server, and check each delivery's signature.

Updated October 18, 2026

A webhook sends a message to another app the moment something happens: a visitor sends a form, a prospect leaves their details, a client uploads photos, a post goes live. Zapier, Make, n8n, GoHighLevel and most CRMs can catch these, so you can build any automation you like without waiting for us to add an integration.

Where to set them up

  • Settings → Integrations: webhooks for your whole agency. Each one gets events from All clients (and new ones), or Only the clients I choose. Owners and Admins manage these.

  • A client's Client → Settings, in the Integrations section: webhooks for just that client, for example the client's own GoHighLevel. Anyone with the Clients tool who can reach that client can manage these. They save as you go; you don't need Save Changes. On a brand-new client, save the client first.

  • One Contact Form only: open the form's Manage → Integrations → Webhook in the editor. That one has its own field names, JSON or form-fields format and custom headers. See How to set up contact forms.

You can have up to 20 webhooks in each place (20 agency-wide, and 20 on each client). Each has a name, an address, the events it gets, an on / off switch and its own signing secret.

How to add a webhook

  1. Open Settings → Integrations (or the client's Client → Settings → Integrations) and click Add webhook.
  2. Under Sending to, pick Any app (standard webhook), Zapier, Make, n8n or GoHighLevel (form submissions). The hint underneath says where to find the address in that app.
  3. Give it a Name (up to 80 characters) and paste the Webhook URL. It must be a public https:// address; redirects aren't followed and private or internal addresses are refused.
  4. Tick the Events it should get. For an agency webhook, choose All clients (and new ones) or Only the clients I choose, then search and tick clients (the count shows as "3 chosen").
  5. Click Add webhook. Your signing secret is shown once: copy it now if the receiving app will check signatures, then click I've saved it. Afterwards it shows masked (whsec_••••abcd).
  6. Pick an event in the dropdown next to Send test and click Send test. A sample of that event arrives with "test": true, so the other app can learn the fields. You'll see "Test delivered (reply 200)." when it works.

Each webhook's card

  • The name, a GoHighLevel badge for GoHighLevel webhooks, and a status badge: On, Off (you switched it off) or Switched off (failing) (we switched it off; a red box says when and why).
  • The address, and a badge for each event it gets.
  • Clients: All clients or Only: … (agency webhooks only).
  • Last delivered, and in red Failing since with how many tries failed, when deliveries are failing.
  • Signing secret (masked) with Rotate.
  • Edit: change the name, address, events and clients, then Save changes. You can't switch an existing webhook to or from GoHighLevel; add a new one instead.
  • Switch off / Switch on: stop or restart sending without deleting it. Events that happen while it's off aren't sent later.
  • Delete: click it, then Yes next to Delete?.
  • Send test and Delivery log.

Events

The name you see when ticking events comes first, then the event name the other app receives.

  • Form submitted (form.submitted): A visitor sends a Contact Form on one of the client's websites. Includes the form, every field, the page address and its UTM tags.
  • Prospect lead captured (lead.created): Someone uses your prospect tool and leaves their details. Agency-wide: it has no client, a client filter doesn't apply, and it isn't offered on a client's own webhooks.
  • Onboarding form submitted (onboarding.submitted): A new client sends your onboarding form.
  • Client uploaded photos or videos (media.uploaded): The client adds photos or videos (portal Media tab, their upload link or the editor). Uploads within 2 minutes arrive together in one message.
  • Video ready (video.ready): An uploaded video has finished processing and can be played.
  • Website published (site.published): A website, or one page of it, is published.
  • Blog post published (blog.published): A blog post goes live, now or at its scheduled time.
  • Work logged (work_item.created): A piece of work done is recorded for the client, automatically or added by hand.
  • Zeus scan finished (zeus.scan.completed): Zeus finishes scanning a client, with how many edicts, decrees and omens he raised.
  • New review (review.new): A new Google review is found when the client's website reviews are refreshed (weekly).
  • Citation status changed (citation.status_changed): A business listing's build status changes (not started, submitted, pending verification, live, needs attention), by your team or the automatic re-check. Includes the directory, the listing address, the old and new status, and who changed it ("automatic" for the re-check). Changing several listings at once sends one message per listing.
  • Social post posted (social.post.posted): A social post is marked as posted. Includes the network, the start of the caption, the link, the live post's address (if you added it), how many photos or videos it has, the pack, where it came from (blog, page, content, topic, zeus or manual), when and who. Sent once per post, even if it's marked as posted again.
  • Press release distributed (press_release.distributed): A press release is marked as distributed, or our distribution publishes it. Includes the headline and sub-headline, the announcement type, the pickups (outlet, live link, date live) and how many, how it was sent (sent_by: distribution_service, other_service or agency), when and who. Sent once per release, even if it's marked again.

A GoHighLevel webhook only gets Form submitted.

What a delivery looks like

Each delivery is a POST with a JSON body:

{
  "id": "6f1c0e9a-…",
  "event": "form.submitted",
  "created_at": "2026-10-11T15:00:00.000Z",
  "agency": { "id": "…", "name": "Your Agency" },
  "client": { "id": "…", "name": "Vista Roofing" },
  "site": { "id": "…", "name": "Vista Roofing", "url": "https://vistaroofing.com" },
  "data": {
    "submission_id": "…",
    "form": { "id": "contact-form", "title": "Contact us" },
    "fields": { "Name": "Jo Smith", "Email": "jo@example.com", "Message": "…" },
    "files": [],
    "page_url": "https://vistaroofing.com/contact?utm_source=google",
    "utm": { "source": "google", "medium": null, "campaign": null, "term": null, "content": null },
    "submitted_at": "2026-10-11T15:00:00.000Z"
  }
}

client and site are null when an event has none. The id stays the same on every retry and resend, so use it to ignore repeats. The names in the body never mention our product, so you can pass them on to your own clients.

Headers: X-Webhook-Id, X-Webhook-Event, X-Webhook-Timestamp, X-Webhook-Attempt and X-Webhook-Signature.

Zapier

  1. In Zapier, create a Zap. Choose Webhooks by Zapier as the trigger and Catch Hook as the event. Copy the webhook URL.
  2. Here, click Add webhook, pick Zapier, paste the URL, choose your events and save.
  3. Click Send test, then Test trigger in Zapier. The sample appears with every field (for example data → fields → Email).
  4. Add your action, like creating a CRM contact, and map the fields. If one Zap gets several events, add a Filter step on event.

A native Zapier app isn't available. The Catch Hook trigger above does the same job.

Make

  1. In Make, start a new scenario and add Webhooks → Custom webhook. Click Add and copy the address.
  2. Add it here with Make as the app, then click Send test while Make shows it's listening, so it learns the structure.
  3. To handle several events in one scenario, add a Router with a filter on event.

n8n

  1. Add a Webhook node with the HTTP method set to POST. Copy the Production URL and activate the workflow. The Test URL only works while you click Listen.
  2. Add it here with n8n as the app and click Send test.

GoHighLevel

A GoHighLevel webhook gets Contact Form submissions with the fields submissionId, siteId, formId, fields and timestamp, the same fields GoHighLevel always got. To add one, go to the client's Client → Settings → Integrations, pick GoHighLevel (form submissions) and paste the URL of a workflow's Inbound Webhook trigger (in GoHighLevel: Automation → Workflows). A client's older GoHighLevel form webhook shows here too, with a note that it was moved from the old field; it keeps sending the same fields.

Check the signature

Check signatures on your own server, so you know a delivery really came from you. X-Webhook-Signature looks like t=1760194800,v1=5257a8…:

  1. Take t (the time in seconds) and the raw request body, exactly as received.
  2. Work out HMAC-SHA256 of t + . + raw body, using your signing secret, as lowercase hex.
  3. Compare it, in constant time, with the v1 value. For 24 hours after you rotate the secret the header carries two v1 values; either one may match.
  4. Refuse the delivery if t is more than 5 minutes from now.
const crypto = require('crypto')

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map(p => p.split('=')))
  const signed = header.split(',').filter(p => p.startsWith('v1=')).map(p => p.slice(3))
  const expected = crypto.createHmac('sha256', secret).update(parts.t + '.' + rawBody).digest('hex')
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300
  return fresh && signed.some(s => s.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)))
}

Rotate the secret

Click Rotate next to the masked secret. The new secret is shown once. The old one keeps working for 24 hours so you have time to update the receiving app.

Delivery log, retries and Resend

Click Delivery log on a webhook to see the last 30 days: When, Event (with a Test or Resent badge), Status (Delivered, Retrying with the next try time, Failed or Canceled, plus the error), Reply (the reply code), Time, Tries, and Payload to see what was sent and the reply body. Resend sends the same message again. Refresh reloads the list.

  • The receiving app must reply with any 2xx within 10 seconds. Anything else counts as a failure.
  • A failed delivery is tried again after 1 minute, 10 minutes, 1 hour, 6 hours and 17 hours: 6 tries over about a day.
  • If every delivery to a webhook fails for 3 days in a row, we switch it off. Its card shows Switched off (failing) and a red box with the date and the last error. There's no email about it, but you get a Webhook switched off notification in the bell, and the card's badge shows it too. Fix the receiving app, then click Switch on.
  • Send test sends once, isn't retried and never counts towards switching a webhook off.

Examples

  • Every lead into your CRM: one agency webhook to Zapier with Form submitted and Prospect lead captured, All clients (and new ones), then a Zap that creates a CRM contact.
  • A client's own GoHighLevel: on that client's Client → Settings → Integrations, add GoHighLevel (form submissions) with their workflow's Inbound Webhook URL.
  • Tell your team in chat when a site goes live: a Make scenario on Website published and Blog post published that posts to your team channel.
  • A monthly report of work done: an n8n workflow on Work logged that adds a row to a spreadsheet.

Limits and what it can't do

  • Up to 20 webhooks agency-wide, and up to 20 on each client.
  • https:// addresses only, up to 2,048 characters. Redirects aren't followed. Private and internal addresses are refused.
  • Names up to 80 characters. At least one event per webhook.
  • Send test: up to 10 a minute and 60 an hour for your agency.
  • The delivery log keeps 30 days.
  • Each delivery waits 10 seconds for a reply.
  • Events aren't queued while a webhook is switched off, and a native Zapier app isn't available (use Catch Hook).
  • Webhooks only send. They can't receive data into ContentForge.

Who can use it

  • Settings → Integrations (agency webhooks): the Owner and Admins.
  • A client's webhooks: anyone with the Clients tool (under Client Management) who can see that client. Client people can't see or change webhooks.

Troubleshooting

  • "You can have up to 20 endpoints here — delete one first": you've reached 20 in this place. Delete one you don't use, or put the rest on the client's own page.
  • "The address must start with https://", "Enter a full address like https://hooks.zapier.com/hooks/catch/…" or "That address is too long": paste the full secure address the other app gave you.
  • "Give the endpoint a name (up to 80 characters)" or "Choose at least one event": fill in the name and tick an event. Add webhook stays grayed out until you do.
  • "The GoHighLevel preset only sends form submissions": GoHighLevel webhooks only get Form submitted.
  • "Choose the clients from the list": with Only the clients I choose, tick at least one client.
  • "The test didn't go through: Replied 404" (or another code): the other app refused it. Check the address, and that the Zap, scenario or workflow is switched on and listening.
  • "Replied 301 (redirects aren't followed — use the final address)": use the address the redirect goes to.
  • "No reply within 10 s": the receiving app is too slow. Make it reply first and do its work afterwards.
  • "The test didn't go through" or "Check your connection": the test couldn't be sent from your browser. Check your internet connection and try again.
  • "You're going too fast — try again in …": you've sent too many tests. Wait the time shown.
  • "Only agency owners and admins can manage agency webhooks": Members can't use Settings → Integrations. Ask an Owner or Admin.
  • "Tool access required" on a client's webhooks: the Clients tool is switched off for you.
  • Deliveries stopped arriving: look for Switched off (failing) on the card. Fix the receiving app, then click Switch on. Use Resend in the log for anything you missed in the last 30 days.
  • The signature doesn't match: check the raw body, not a re-encoded copy, and that you're using the current secret. After Rotate, the old secret still works for 24 hours.

Didn't find what you need?

Send us a message — no sign-in needed — or email support@the-contentforge.com. Already a customer? Sign in to submit a ticket.

Contact support