Skip to main content
Instead of polling us for changes, register an endpoint and we’ll POST to it as things happen. A message arriving, a lead reaching your Won stage, a comment on a Facebook post — each becomes an HTTP request to a URL you control. This is what you want if you’re driving n8n, Zapier, Make, or anything you’ve written yourself. See the n8n guide for a worked example. Webhooks are configured in the app, not through the API. Go to Integrations → Webhooks. An endpoint is somewhere we send your customers’ data, so creating one is deliberately an administrator action rather than something an API key can do.

Setting one up

1

Add the endpoint

Integrations → Webhooks → New endpoint. Give it a name and a publicly reachable https:// URL.
2

Choose your events

Tick the events you want. For each one you can also tick which fields the payload should carry — see choosing fields.
3

Save the signing secret

It’s shown once, on creation, and starts with whsec_. You need it to verify signatures. Store it in your secret manager before closing the dialog.
4

Send a test

Use Send test on the endpoint’s page. It goes through the real delivery path — same signing, same logging — so a test that arrives proves the setup works.
The URL must resolve to a public address. Private ranges, localhost, link-local and cloud metadata addresses are rejected, both when you save the endpoint and again on every delivery — so a hostname that is later repointed inward stops working rather than becoming a way into our network.Testing locally? Use a tunnel such as ngrok or n8n’s own test URL, not http://localhost.

Events

Editing a lead’s stage fires both lead.updated and lead.stage.changed. Subscribe to the one you actually mean — taking both means handling each move twice.lead.won and lead.lost follow the won/lost flags on your pipeline stages, not the stage name. If you renamed “Won” to “Closed — signed”, it still fires.

The payload

Every delivery has the same envelope. Only data differs by event:
source is worth using if your workflow writes back into Linkiasoft: it lets you tell a change your own automation made from one a person made, which is how you avoid a loop.

Choosing which fields to send

Each event has a set of fields, and you pick which ones we include. Untick anything the receiving system doesn’t need — particularly text, customer_phone and email, which are the fields you’re least likely to want sitting in a third-party tool’s logs. Leaving all fields ticked is not the same as listing them out. All-ticked means “send whatever this event carries”, so a field we add later is included automatically. If you untick even one, you get exactly the set you chose and nothing new.

Verifying the signature

Every request carries these headers: v1 is an HMAC-SHA256, keyed with your signing secret, over the string `${t}.${rawBody}` — the timestamp, a literal dot, then the raw request body.
Sign the raw body exactly as received. If your framework parses the JSON and you re-serialise it before hashing, key order or whitespace will differ and every signature will fail. Most frameworks need to be told to keep the raw body.
Compare digests with a constant-time function — timingSafeEqual, compare_digest, hash_equals — not ==.
The timestamp is inside the signed string on purpose. Signing the body alone would leave every delivery you receive replayable by anyone who captured one, forever. Checking t against a tolerance is the half that makes it useful, so don’t skip it.

Retries

Answer 2xx and we consider the delivery done. Anything else: Retries back off: 30 seconds, 2 minutes, 10 minutes, 1 hour, 6 hours — five attempts across roughly eight hours, then we stop. Every attempt appears in the log with its own X-Linkia-Attempt number, and all attempts at one delivery share the same envelope id.
Delivery is at least once. A network failure after your handler succeeded but before your 2xx reached us means you’ll see the same event again. If reprocessing would double a charge, send a duplicate message, or create a second record, deduplicate on the envelope id — it is stable across every attempt.
Twenty consecutive failures disables the endpoint. We stop calling it, the page shows why, and events stop being queued for it. This exists so an abandoned test URL doesn’t generate failing deliveries forever. Fix the endpoint and press Enable again — that clears the streak. Nothing is replayed automatically; use the log to re-send what matters.

The delivery log

Each endpoint keeps its most recent 300 attempts, with the exact body we sent, the first 2KB of your response, the status code, and how long it took. Open one to see the request and response side by side, or press the retry icon to send it again. Lifetime totals — how many requests we’ve ever made and how many failed — are counted separately and are not affected by the 300-row limit.
The log is a debugging tool, not an archive. On a busy endpoint 300 attempts can be under an hour. If you need durable history, record deliveries on your side keyed by envelope id.

Custom headers

If your endpoint sits behind a proxy that wants its own header, add it under the endpoint’s settings. Headers we set ourselves — Content-Type, User-Agent and every X-Linkia-* header — can’t be overridden, since a custom header that could replace the signature would make deliveries trivially forgeable.

Rotating the secret

Rotate secret on the endpoint page issues a new one and shows it once.
There is no overlap window. The old secret stops verifying the moment the new one is issued, so deploy the new secret to your receiver first, or expect failures in between. Those failures count toward the auto-disable streak.

Things to watch in production

A slow endpoint costs you events, not us. We wait 10 seconds. If your handler does real work — calling other APIs, writing to a slow database — answer 2xx immediately and process in the background. A handler that answers in 11 seconds looks identical to one that is down. Don’t return 4xx for your own problems. A 422 because your validation is too strict is terminal on our side: we won’t retry, and the event is gone. Return 5xx for anything you’d want another attempt at. Test deliveries look like real ones. webhook.test arrives through the same path with a valid signature. Handle or ignore the event name; don’t assume every delivery is a real domain event.