Groundbase
Start free trial

Webhooks come in two flavours, both in Settings → Webhooks:

Pick the tab in Settings → Webhooks for the direction you want.

Outbound webhooks

Adding an outbound webhook

Settings → Webhooks → Add webhook.

  1. Endpoint URL — where we POST events (must be https:// for production)
  2. Description — optional internal label
  3. Events — tick "All events (*)" to get everything, or pick specific event types (chips below the All toggle)
  4. Click Add webhook
  5. Save the signing secret that appears in the dialog. We never show it again. Lose it → just delete the webhook and create a new one.

What we send

Every event is a POST request with this JSON body:

{
  "id": "f8a1c4e2-9b3d-4f72-a8e1-1c2b3d4e5f60",
  "type": "contact.created",
  "created_at": "2026-06-09T14:23:11.842Z",
  "user_id": "a26a6272-63d9-421e-a5ef-a9398620f739",
  "entity_id": "b502bd84-7482-4c6c-981a-3762ee26ba6f",
  "data": {
    "contact_id": "b502bd84-7482-4c6c-981a-3762ee26ba6f",
    "first_name": "Adam",
    "last_name":  "Rao",
    "email":      null,
    "phone":      "14164515566"
  }
}

…and these headers:

Content-Type:   application/json
User-Agent:     Groundbase-Webhook/1.0
X-GB-Event:     contact.created
X-GB-Delivery:  <uuid> — unique per attempt; survive your idempotency check
X-GB-Signature: sha256=<64 hex chars>

Verifying the signature

X-GB-Signature is sha256=<hex of HMAC-SHA256(secret, raw body bytes)>. Compute the same hash on your end and compare — equal means the payload came from us and wasn't tampered with.

Node.js / Express:

const crypto = require('crypto');

app.post('/webhooks/groundbase', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-gb-signature'] || '';
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.GB_WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');
  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return res.status(401).send('Bad signature');
  }
  const event = JSON.parse(req.body.toString());
  console.log('Got', event.type, event.entity_id);
  res.status(200).send('ok');
});

Python / Flask:

import hmac, hashlib, os
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ['GB_WEBHOOK_SECRET'].encode()

@app.route('/webhooks/groundbase', methods=['POST'])
def hook():
    sig = request.headers.get('X-GB-Signature', '')
    expected = 'sha256=' + hmac.new(SECRET, request.data, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(sig, expected):
        abort(401)
    event = request.get_json()
    print('Got', event['type'], event['entity_id'])
    return 'ok', 200

Reliability

Available event types

24 event types currently fire:

Contactscontact.created, contact.updated, contact.deleted Companiescompany.created, company.updated, company.deleted Dealsdeal.created, deal.updated, deal.deleted, deal.stage_changed (includes from/to stage) Taskstask.created, task.updated, task.completed, task.deleted Notesnote.created Tagstag.applied SMSsms.sent, sms.received, sms.delivered (Twilio confirmation), sms.failed Callscall.completed, voicemail.received Emailemail.sent

Use ["*"] as the events array to subscribe to all of them.

Limits

Test it before going live

In the Settings → Webhooks list, click Test on a subscription row. We queue a synthetic contact.created event with entity_id="test-event" and data.test=true. Should hit your endpoint within ~60 seconds (the per-minute delivery cron).

webhook.site is the easiest sandbox — open the page, copy the unique URL it gives you, paste it into a Groundbase webhook subscription, fire a Test, and watch the request show up live.

Inbound webhooks

Inbound webhooks let external systems push data into Groundbase. Each inbound webhook gives you a unique URL — when something POSTs JSON to that URL, Groundbase creates contacts, applies tags, drops notes, etc. based on the body.

Adding an inbound webhook

Settings → Webhooks → Inbound tab → Add inbound webhook.

  1. Name — a label like "Facebook Lead Ads" or "Typeform contact form"
  2. Description — optional internal notes
  3. Require HMAC signature — optional extra security layer. Skip unless your sender supports it (Stripe, GitHub, your own code). The URL token alone is already a long random secret.
  4. Click Create webhook
  5. Save the URL that appears. We never show the full URL again. (You can always rotate to a fresh one from the list.) If you ticked HMAC, save the signing secret too.

How it works — native mode

The simplest setup: POST a JSON body in Groundbase's native shape.

curl -X POST 'https://api.groundbasecrm.com/inbound/<your-token>' \
  -H 'Content-Type: application/json' \
  -d '{
    "event": "contact.create",
    "data": {
      "first_name": "Jane",
      "last_name":  "Doe",
      "email":      "jane@example.com",
      "phone":      "+14165551234",
      "tags":       ["lead", "fb-ads"]
    }
  }'

That single POST creates a contact AND applies both tags. Most automation tools (Zapier, Make, n8n) can shape any payload into this format — point your tool at the URL and configure the body template.

Supported native events

Tags are case-insensitive and auto-created on first use.

Facebook Lead Ads example — mapped mode

FB Lead Ads sends payloads that don't match Groundbase's native shape. Use Mapped mode in Settings → Webhooks → Inbound → Add inbound webhook → "Mapped" tile.

  1. Paste a real sample FB Lead payload into the Sample payload box. Groundbase parses it and lights up every field path so you can pick from a dropdown.
  2. Click Add action. Pick contact.find_or_create as the event.
  3. Map the FB fields onto Groundbase fields:
    • first_namelead.full_name.first (or wherever FB puts it in your sample)
    • last_namelead.full_name.last
    • emaillead.email
    • phonelead.phone_number
  4. Add a static value: key = tags, value = lead,fb-ads — Groundbase will apply both tags to every new contact.
  5. Add a second action: note.create with body field mapped to a custom message and contact_id mapped from the contact you just upserted (or use email/phone matching — contact.find_or_create returned the contact, and any subsequent action with the same email/phone resolves to the same row).
  6. Live preview at the bottom of each action card shows exactly what Groundbase will receive for that action — verify before saving.

The two actions run in order on every POST. Re-saves are immediate. Use the edit icon on the row to come back and tweak the mapping later.

If your sender isn't supported well by mapped mode, you can also use native mode through a middleman like Zapier or Make — set the trigger to "New Lead in Facebook Lead Ads", set the action to "Webhook POST" with your Groundbase inbound URL, and build the body in { event, data } shape.

Security

Reliability

Test it

# Quick smoke test from a terminal:
curl -X POST 'https://api.groundbasecrm.com/inbound/<your-token>' \
  -H 'Content-Type: application/json' \
  -d '{"event":"contact.create","data":{"first_name":"Test","email":"test@example.com","tags":["webhook-test"]}}'

Should return {"ok": true, "status": "processed", "actions": [...]}. Refresh your contacts list — there should be a "Test" contact with the webhook-test tag applied.