What it means
A webhook is a way for one system to notify another that something happened, in real time, with the relevant data already attached. Mechanically it is just an HTTP POST: the system where the event occurred (the sender) calls a URL hosted by your system (the receiver) and includes a JSON body describing the event. Your code processes the body and does whatever it needs to do.
The pattern shows up everywhere there is integration work: Stripe POSTs to your URL when a payment succeeds; GitHub POSTs when a commit lands; CRM Solid POSTs when a new message arrives or a deal moves stage; Calendly POSTs when a meeting is booked. The receiver can chain reactions (fire an AI agent, update a database, send a notification) without ever having to ask "anything new?".
The term was coined by Jeff Lindsay in a 2007 blog post, but the pattern was already in production at PayPal years earlier. By 2026 it is the lingua franca of SaaS integration.
Webhooks vs polling
Both patterns connect two systems. The difference is who initiates the conversation.
- Polling. Your system calls theirs every N seconds asking "is there anything new?". Most of the time the answer is no. The call is wasted; the data you do get is N seconds stale; both sides pay for the bandwidth.
- Webhook. Their system POSTs to your URL the instant something happens. Latency is sub-second. Bandwidth is zero when nothing is happening. Both sides save on cost and complexity.
Webhooks win on every axis except one: they require your system to expose a public HTTPS endpoint. If you cannot (firewall, intranet, mobile app), polling is the fallback, but it is always second-best.
HMAC signing for security
Anyone can POST to your URL. You need a way to verify that the POST came from the sender you trust, and that no one has modified the payload in transit. The standard answer is HMAC signing:
- The sender and receiver share a secret (a long random string, exchanged out of band).
- Before sending, the sender computes
HMAC-SHA256(secret, payload)and includes the result in a header, typicallyX-SignatureorX-Hub-Signature-256. - On receipt, the receiver computes the same hash with the same secret and compares to the header. If they match, the request is authentic. If they do not, reject it.
HMAC alone defends against tampering and most forgery. To defend against replay attacks (where an attacker captures a valid request and re-sends it), include a timestamp in the signed payload and reject requests older than (say) 5 minutes.
Additional hardening:
- IP allowlisting: only accept POSTs from the sender's documented IP ranges.
- HTTPS only: never expose a webhook endpoint on plain HTTP.
- Idempotency keys: every webhook delivery should carry a unique ID; record processed IDs and ignore duplicates.
- Constant-time signature comparison: use a constant-time string comparison function (e.g.,
hmac.compare_digestin Python) to avoid timing attacks.
Retry semantics
Networks are unreliable. Your service might be down. A production-grade webhook sender retries on failure with exponential back-off. The standard pattern:
- Immediate first delivery.
- Retry at 1 minute on failure.
- Retry at 5 minutes.
- Retry at 30 minutes.
- Retry at 2 hours.
- Retry at 6 hours.
- Retry at 24 hours.
- Give up and log a "delivery failed" event in the sender's dashboard.
The receiver is responsible for being idempotent: processing the same event twice must produce the same result as processing it once. Without this, retries cause double-charges, double-emails, duplicate database rows. Always inspect the idempotency key (or event ID) before processing.
"Success" from the receiver is a 2xx response within 5 seconds. Anything else (4xx, 5xx, timeout) triggers the retry cycle. Long-running processing should be queued asynchronously: respond 2xx immediately, do the work in a background job.
Common webhook payloads
The shape varies by sender, but the canonical structure is:
- Headers carry metadata:
Content-Type: application/json, signature, timestamp, event type, delivery ID. - Body is JSON describing the event. Most senders use a top-level
typeoreventfield plus adataobject.
A representative example:
(See the cheat sheet below for the full anatomy.)
Why it matters
Webhooks are the difference between a CRM that lives in a silo and one that connects to the rest of your stack. Every time you want "when X happens in CRM Solid, do Y in something else", a webhook is the answer. Without webhooks you write a polling loop, accept the latency, and pay for the bandwidth.
For an AI agent use case, webhooks are also the trigger mechanism. An agent "wakes up" when a webhook fires (new message, new deal, missed meeting), does its work, then sleeps until the next event. No polling, no cron job sweeping a database every minute. Just push-based reaction.
Real-world examples
- New-message → AI agent. A new Telegram DM arrives in CRM Solid. The platform POSTs a
message.createdwebhook to your agent's URL. The agent classifies the message, drafts a reply, and either sends it or escalates to a human. - Deal moved → Slack notification. A deal moves to "Negotiation" in the pipeline. CRM Solid POSTs a
deal.stage_changedwebhook to a Zapier or direct integration; Slack posts "Aisha's $30k deal just moved to Negotiation" to your sales channel. - Stripe payment → email + CRM tag. Stripe POSTs
payment.succeededto your endpoint. Your service tags the contact as "paid customer", fires the post-purchase drip, and updates the deal stage. - Calendly booking → meeting prep. Calendly POSTs
invitee.createdto your URL when a prospect books. Your agent fetches the prospect's CRM record, generates a one-page brief, and emails it to the rep. - Flood wait → ops alert. CRM Solid detects a Telegram
FLOOD_WAITon an account and fires a webhook to your monitoring stack so the team can adjust pacing in real time.
Debugging tips
- Log everything. Save every incoming webhook (headers + body) to a tabular log. When something fails, you can replay the exact request.
- Use a webhook-inspection tunnel during development. Services like ngrok, smee.io, or webhook.site expose a local server publicly so you can receive real webhooks against your dev box.
- Test signature verification with the sender's docs. Most senders provide an example payload and signature in their docs; verify against that before you go live.
- Respond fast. The 5-second timeout is real. Acknowledge immediately, process in a background queue.
- Build a replay tool. When something breaks, you want to "re-deliver event 12345" with one click. Stripe, Calendly, GitHub and CRM Solid all expose this; design your integration to take advantage.
Common mistakes
- No signature verification. Anyone who guesses your URL can POST whatever they want. HMAC is non-optional.
- Not idempotent. Retries (which WILL happen) double-process. Always check the idempotency key before doing real work.
- Slow synchronous handlers. If your receiver takes 8 seconds to respond, every webhook triggers a retry, and you process the same event twice. Respond 2xx in under a second; do the work async.
- No retry tolerance on the sender side. If you are sending webhooks, retry on failure. Receivers go down; networks blip. A webhook sender that gives up on first failure loses events.
- Logging payloads with secrets. Webhook payloads often contain PII or secrets (API tokens, session blobs). Redact before logging.
Related concepts
- AI agent: webhooks are the standard trigger for agent loops.
- Omnichannel CRM Webhooks push channel events into the unified record in real time.
- Flood wait: a Telegram-specific event that often fires a monitoring webhook.
- MTProto: the protocol layer whose events translate into webhook fires.
- Sales pipeline Deal events (stage change, close) are the most common outbound webhook category.
- Drip campaign: webhooks trigger drips on real-world events (signup, purchase).
How CRM Solid handles it
CRM Solid exposes outbound webhooks for every important event: message.created, contact.updated, deal.stage_changed, account.flood_wait, job.completed, and more. Every webhook is HMAC-signed with a per-workspace secret, includes a unique idempotency key, retries on failure with exponential back-off, and is replayable from the dashboard. Inbound webhooks (from Stripe, Calendly, etc.) integrate via the public API.