Webhooks

Webhooks tell your system the moment something happens in a SmartContact workspace: a company added, a contact changed, a deal won, a task completed. Every change in SmartContact is recorded together with its event, so events are never lost, and they reach you within about a minute.

Subscribe

Admins can add a webhook in Settings › Webhooks. Integrations subscribe through the API with their key:

POST https://app.smartcontact.online/v1/webhooks
Authorization: Bearer sc_…
Content-Type: application/json

{ "url": "https://example.com/smartcontact-events", "events": ["company.created", "deal.updated"] }

The answer includes a secret, shown only once. Remove the webhook with DELETE /v1/webhooks/{id}, or answer any delivery with 410 Gone. A key only sees and removes its own webhooks, and can only subscribe to events about data its role can read. Plugins declare a webhook URL and events on their version in the developer portal; installing the plugin subscribes them.

What you receive

POST https://example.com/smartcontact-events
Content-Type: application/json
X-SmartContact-Event: deal.updated
X-SmartContact-Delivery: 6a1c…   (the same on every retry: use it to ignore repeats)
X-SmartContact-Signature: t=1792569600,v1=5f2b…

{
  "id": "evt_10423",
  "type": "deal.updated",
  "created_at": "2026-10-21T09:41:07.000Z",
  "workspace_id": "…",
  "data": { "id": "…", "changed": ["stage_id"] },
  "actor_type": "user",
  "api_url": "https://app.smartcontact.online/v1/deals/…"
}

Events carry ids and the names of changed fields, never names, emails or notes. Fetch api_url with your key to get the record as your key is allowed to see it. When you receive contact.erased or company.erased, delete everything you hold about that record: the person asked to be forgotten.

Check the signature

v1 is the hex HMAC-SHA256 of <t>.<raw body> with your secret (a plugin's events use the plugin's signing secret). Refuse it if it doesn't match or t is more than 5 minutes old.

import { createHmac, timingSafeEqual } from "node:crypto";

function isFromSmartContact(secret, header, rawBody) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const want = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest();
  const got = Buffer.from(parts.v1 ?? "", "hex");
  return got.length === want.length && timingSafeEqual(got, want);
}

Retries and switching off

  • Answer with any 2xx within 10 seconds. Redirects are not followed.
  • Anything else is retried after 1, 5 and 30 minutes, then 2, 6, 12 and 24 hours, then given up. Admins can see every delivery in Settings › Webhooks.
  • After 20 failures in a row the webhook is switched off. It is also switched off if the key that made it is revoked, the plugin is uninstalled or the person leaves.
  • Events may arrive more than once, and in rare cases out of order: use X-SmartContact-Delivery and created_at.

Events (33)

EventWhenNeeds
company.created A company was added Companies (read)
company.updated A company changed (data.changed lists the fields) Companies (read)
company.deleted A company went to the recycle bin Companies (read)
company.restored A company came back from the recycle bin Companies (read)
company.merged A duplicate company was merged into another Companies (read)
company.erased A company was erased: delete any copy you hold Companies (read)
company.contact_added A contact was added to a company Companies (read)
company.deal_added A deal was added to a company Companies (read)
contact.created A contact was added Contacts (read)
contact.updated A contact changed (data.changed lists the fields) Contacts (read)
contact.deleted A contact went to the recycle bin Contacts (read)
contact.restored A contact came back from the recycle bin Contacts (read)
contact.merged A duplicate contact was merged into another Contacts (read)
contact.erased A person was erased: delete any copy you hold Contacts (read)
contact.consent_changed Marketing consent was given or withdrawn Contacts (read)
deal.created A deal was added Companies (read)
deal.updated A deal changed, including its stage, won or lost Companies (read)
deal.deleted A deal went to the recycle bin Companies (read)
deal.restored A deal came back from the recycle bin Companies (read)
task.created A task was added Tasks (read)
task.updated A task changed Tasks (read)
task.completed A task was completed Tasks (read)
meeting.created A meeting was booked Companies (read)
activity.created A note, call or email was added to a timeline Companies (read)
quote.created A draft quote was started on a deal Companies (read)
quote.sent A quote was sent to the customer (its accept link now works) Companies (read)
quote.viewed The customer opened a quote for the first time Companies (read)
quote.accepted The customer accepted a quote (the deal is marked won) Companies (read)
quote.declined The customer declined a quote Companies (read)
quote.revised A sent quote was reopened for changes (its old link stopped working) Companies (read)
quote.voided A quote was withdrawn Companies (read)
quote.invoiced A draft invoice was created in Xero for an accepted quote Companies (read)
quote.paid The quote's Xero invoice was paid Companies (read)