Skip to content
Open app

Webhooks

Webhooks let you receive real-time event notifications from CRM Solid in your own systems — no polling required. When something happens (a new contact, an inbound message, a completed sequence), the server immediately POSTs a signed JSON envelope to every matching HTTPS endpoint you have registered.

Every delivery carries an X-Webhook-Signature header computed as HMAC-SHA256(secret, raw_body) — always verify it before trusting the payload. Endpoints are managed in Settings → Developers; you may register up to 20 endpoints per account.

  1. Open Settings → Developers in the panel.

  2. Register a new webhook endpoint by providing an HTTPS URL where deliveries should be POSTed. Webhooks are only delivered to HTTPS endpoints.

  3. Save the endpoint and copy its signing secret. This secret is used to compute and verify the X-Webhook-Signature of every delivery — store it securely.

  4. (Optional) Click Send test event to receive a synthetic webhook.test delivery and confirm your receiver works end to end.

Every webhook POST includes the following headers:

HeaderValue
X-Webhook-Signaturesha256=<hex>HMAC-SHA256(secret, raw_body)
X-Webhook-Evente.g. contact.created
X-Webhook-Event-IdUUID of this event (idempotency key)
Content-Typeapplication/json; charset=utf-8
EventWhen it fires
contact.createdFires when a new CRM contact is created (any source — manual, import, scrape, inbound DM).
message.sentFires when an outbound message (Telegram or X DM) is successfully delivered.
message.receivedFires when an incoming Telegram or X DM is recorded against a contact.
sequence.completedFires when a campaign sequence finishes processing all of its targets.
subscription.changedFires when the user’s plan changes (upgrade, downgrade, cancel, renew).
webhook.testSynthetic event sent when you click “Send test event” — safe to ignore in production flows.

Every delivery is a JSON object with four top-level keys. The data field carries the event-specific payload.

FieldDescription
idUnique delivery ID (same across retries)
typeEvent type string, matches X-Webhook-Event
createdAtISO-8601 UTC timestamp of the event
dataEvent-specific payload object

A contact.created delivery looks like this:

{
"id": "evt_01HXYZ1234ABCDEF",
"type": "contact.created",
"createdAt": "2026-05-07T14:23:01.000Z",
"data": {
"contact": {
"id": 8821,
"name": "Anya Ivanova",
"username": "anya_ivanova",
"platform": "telegram",
"createdAt": "2026-05-07T14:23:00.817Z"
}
}
}
{
"id": "evt_01HXYZ2345BCDEFG",
"type": "message.sent",
"createdAt": "2026-05-07T14:24:10.000Z",
"data": {
"message": {
"id": 41190,
"contactId": 8821,
"text": "Hello Anya, thanks for joining!",
"platform": "telegram",
"sentAt": "2026-05-07T14:24:09.442Z"
}
}
}

Compute HMAC-SHA256(secret, raw_request_body), hex-lowercase the result, prepend sha256=, and compare to the X-Webhook-Signature header in constant time.

import crypto from 'node:crypto';
// rawBody: Buffer - do NOT parse/re-stringify
// secret: your endpoint's signing secret
// sigHeader: value of X-Webhook-Signature header
function verifyWebhook(rawBody, secret, sigHeader) {
const expected = 'sha256=' +
crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
// Constant-time comparison to prevent timing attacks
const eBuf = Buffer.from(expected);
const rBuf = Buffer.from(sigHeader);
if (eBuf.length !== rBuf.length) return false;
return crypto.timingSafeEqual(eBuf, rBuf);
}

Deliveries are retried up to 8 attempts total on transient failure (5xx or network error). The backoff schedule is fixed.

The same delivery id and X-Webhook-Event-Id are reused across all attempts, so always de-duplicate on the event ID.

How CRM Solid reacts to your receiver’s response status:

StatusResult
2xx SuccessDelivery marked as delivered. No retry. The response body is ignored — only the status code matters.
4xx Client errorDelivery is marked failed immediately. No retry scheduled — a 4xx typically indicates a permanent misconfiguration (wrong URL, bad auth on your receiver, etc.).
5xx / timeout Server / network errorRetried according to the backoff schedule above. After all 8 attempts are exhausted the delivery status becomes “exhausted” and can be manually retried from the Developers panel.

Failed or exhausted deliveries can be manually retried from the delivery log in Settings → Developers. Disabled endpoints can be re-enabled at any time — deliveries fired while the endpoint was disabled will not be replayed automatically.

  • Verify every signature against the raw request body in constant time before trusting a payload.
  • Respond fast with a 2xx. Acknowledge the delivery immediately and process the payload asynchronously — slow responses count as timeouts and trigger retries.
  • De-duplicate on X-Webhook-Event-Id. Retries reuse the same event ID, so make your handler idempotent.
  • Use the raw body for signature verification — never the re-serialised JSON.
  • Monitor for auto-disable. Keep your receiver healthy to avoid 20 consecutive failures disabling the endpoint.
  • Use HTTPS endpoints only. Deliveries are only sent to HTTPS URLs.