Guides

Webhooks

Get an event posted to your URL the moment it happens, instead of polling a trigger.

A webhook is a subscription to one event. When the event happens, ContactZapp sends it to your URL. It carries the same item the trigger for that event lists, so a workflow can use either.

Subscribe

Subscribes a URL to one event. Zapier, Make and n8n call this when an automation is switched on. The URL must use HTTPS and reach a public address. The answer is 201 with the subscription and its signing secret, which is shown only here. Needs the webhooks:manage and contacts:read scopes. A user can have up to 25 subscriptions.

The event is one of these:

EventIt happens when
contact.createdA contact was added.
contact.updatedA contact was changed.
contacts.mergedContacts were merged into one.
match.needs_reviewA contact is flagged as a possible duplicate and waits in Review duplicates.
account.needs_attentionA linked account needs the user to look at it.
Request
curl -s -X POST https://app.contactz.app/api/v1/webhooks \
  -H "Authorization: Bearer $CZ_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/hooks/contactzapp", "event": "contact.created"}'
Answer, 201 (shortened)
{
  "id": "…",
  "webhook": { "id": "…", "url": "https://example.com/hooks/contactzapp", "event": "contact.created", "state": "active" },
  "secret": "cz_whsec_…"
}

What a delivery looks like

Each event is sent to the URL as a POST with a JSON body. The body is one item, exactly as the polling trigger for the same event lists it. Every delivery has these headers:

HeaderValue
ContactZapp-Signaturet=<unix seconds>,v1=<signature>, where the signature is the hex HMAC-SHA256 of the timestamp, a full stop and the raw body, keyed with the secret. Check it, and refuse a timestamp more than five minutes from your clock.
ContactZapp-DeliveryThe delivery's id. A retry sends the same id.
ContactZapp-EventThe event, for example contact.created.

Check the signature

Anyone can post to your URL, so check each delivery before you act on it. Compute the HMAC-SHA256 of the timestamp, a full stop and the raw body, keyed with the secret, and compare it with v1. Use the body exactly as it arrived: parse it only after the check. Refuse a timestamp more than 5 minutes from your clock.

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

const TOLERANCE_SECONDS = 5 * 60;

// header: the ContactZapp-Signature value. rawBody: the body as a string, before JSON.parse.
export function isFromContactZapp(header, rawBody, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || !parts.v1) return false;
  if (Math.abs(Date.now() / 1000 - t) > TOLERANCE_SECONDS) return false;
  const expected = createHmac("sha256", secret).update(t + "." + rawBody).digest("hex");
  const given = Buffer.from(parts.v1, "hex");
  const wanted = Buffer.from(expected, "hex");
  return given.length === wanted.length && timingSafeEqual(given, wanted);
}

Answer, retries and duplicates

Answer with any 2xx status. An answer of 410 removes the subscription. Any other answer, or none within 10 seconds, is retried with backoff for about a day; after the last attempt the subscription is turned off. Each event is sent at least once: deduplicate by the item's id.

  • Answer fast. Reply with a 2xx first, and do the work after. A slow answer counts as a failure.
  • Deduplicate by id. A retry sends the same ContactZapp-Delivery, and the item’s id is the same in every delivery of that event. Keep the ids you have handled.
  • A turned-off subscription shows as disabled in GET /api/v1/webhooks, and gets no more deliveries.

Unsubscribe

  1. Send DELETE /api/v1/webhooks/{id} with the id from the subscribe answer. The key needs the webhooks:manage scope. The answer is 204.
  2. GET /api/v1/webhooks lists your subscriptions with their state.

Tools that switch on and off, such as n8n and Make, subscribe when the workflow is switched on and unsubscribe when it is switched off. See the n8n and Make recipes.