Webhooks

Receive signed events when records change, and verify each delivery.

Webhooks send an HTTPS request to your endpoint when something changes in the workspace. They are included on Pro and Enterprise.

#Add an endpoint

  1. Open Settings → Webhooks and choose Add an endpoint.
  2. Enter your HTTPS address and choose the events to send.
  3. Copy the signing secret. You need it to verify deliveries.

#Events

EventSent when
record.createdA record was created.
record.updatedA record was changed.
record.deletedA record was permanently deleted.
opportunity.stage_changedAn opportunity moved stage.
opportunity.wonAn opportunity moved to a won stage.
opportunity.lostAn opportunity moved to a lost stage.
task.completedA task was marked done.
email.receivedAn email arrived in a connected mailbox.
meeting.analyzedA recorded meeting finished analysis.
forecast.snapshot_createdA forecast snapshot was taken.

Each delivery carries the event name in x-syncon-event and a delivery id in x-syncon-delivery. A failed delivery is retried with growing gaps, up to eight attempts.

#Verify a delivery

The x-syncon-signature header looks like t=1729590000,v1=…: a Unix timestamp and an HMAC-SHA256, in hex, of the timestamp, a full stop and the raw request body. Reject a delivery whose timestamp is more than five minutes old or whose signature does not match.

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

// body: the raw request body as a string, before any JSON parsing
export function isGenuine(body, header, secret) {
  const parts = Object.fromEntries(
    (header ?? "").split(",").map((part) => {
      const at = part.indexOf("=");
      return [part.slice(0, at).trim(), part.slice(at + 1).trim()];
    }),
  );
  const timestamp = Number(parts.t);
  if (!Number.isInteger(timestamp) || !/^[0-9a-f]{64}$/.test(parts.v1 ?? "")) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;

  const expected = createHmac("sha256", secret).update(`${timestamp}.${body}`).digest();
  return timingSafeEqual(expected, Buffer.from(parts.v1, "hex"));
}