DocsConcepts
Events and webhooks
When someone outside the agent acts, like booking a meeting or signing a document, Conecto records an event. The agent reads it, or a channel gets it as it happens.
An area records an event when something happens that the agent should hear about: a candidate applies, a customer books, a form comes in, a document is signed, an email arrives, the balance runs low. Each event is kept 30 days, and events and their deliveries are free. Event kinds lists every kind with the fields its data carries.
| Field | What it is |
|---|---|
event_id |
A number that grows with each event. Poll with after set to the last one you saw. |
kind |
What happened, like calendar.booking_. |
title |
One line about it. |
text |
More words, when there are some. |
data |
The facts as JSON. Each kind's fields are on Event kinds. |
link |
A page about it, when there is one. |
occurred_at |
When it happened. |
account_events returns what happened, oldest first. Pass after with the last event_id you saw, and kinds to narrow it, like calendar.*. A read is free up to the month's 1,000,000 free reads.
{
"after": 1520,
"kinds": ["calendar.*", "forms.*", "esign.*"]
}{
"events": [
{
"event_id": 4101,
"kind": "calendar.booking_created",
"title": "Rachel Kim booked Bike fitting",
"text": "Saturday, October 24, 2026 at 11:30 (America/Los Angeles)",
"data": {
"reference": "CUT49YHZ85",
"meeting_type": "bike-fitting",
"start": "2026-10-24T11:30:00-07:00",
"end": "2026-10-24T19:15:00+00:00",
"name": "Rachel Kim",
"email": "rachel.kim@example.com",
"phone": null,
"source": "bot",
"reason": null
},
"link": null,
"occurred_at": "2026-10-14T08:00:00+00:00"
}
],
"next_after": 4101,
"note": null
}next_after is the event_id to pass the next time. A channel that gets events as they happen saves the polling.
A channel is a place the business gets messages: Slack, Microsoft Teams, Discord, Google Chat, a Telegram chat, ntfy, Pushover, or a webhook of its own.
notify_add_channelsaves a channel by name, for $0.001. It sends a test message first, and saves nothing when the test fails to arrive. Its tokens are stored encrypted and never shown back.notify_subscribesends chosen events to it, for $0.001: one kind, every event of an area likehiring.*, the events bots send likecustom.*, or*for all of them, up to 30 names. It replaces the channel's list.notify_unsubscribestops them. The channel stays fornotify_send.
{
"channel": "hiring",
"events": ["hiring.application_received", "hiring.test_completed"]
}A chat app gets the event's title, its text and its link. A webhook gets the whole event as signed JSON.
Deliveries go out within about a minute. Some kinds go out at once, like a chat message and the answer to an approval, and Event kinds marks them. A delivery that fails is tried again after 5 minutes and after 30 more, 3 tries in all. A channel gets at most 30 messages a minute, and the rest wait a minute. An account keeps up to 50 channels.
A webhook channel posts each event to the business's own address, which is how Zapier, Make, n8n or the business's own systems connect. notify_add_channel with kind: "webhook" and a public https url answers with a webhook_secret, shown once.
{
"id": "evt_4101",
"event": "calendar.booking_created",
"title": "Rachel Kim booked Bike fitting",
"text": "Saturday, October 24, 2026 at 11:30 (America/Los Angeles)",
"data": {
"reference": "CUT49YHZ85",
"meeting_type": "bike-fitting",
"start": "2026-10-24T11:30:00-07:00",
"end": "2026-10-24T19:15:00+00:00",
"name": "Rachel Kim",
"email": "rachel.kim@example.com",
"phone": null,
"source": "bot",
"reason": null
},
"link": null,
"account": "Halverson Cycle & Repair",
"occurred_at": "2026-10-14T08:00:00+00:00"
}| Header | What it holds |
|---|---|
Content-Type |
application/json. |
X-Conecto-Signature |
sha256= and the HMAC-SHA256 of the raw body, keyed with the channel's webhook_secret, as hex. |
X-Conecto-Event |
The event's kind, like calendar.booking_. |
X-Conecto-Delivery |
evt_ and the event's event_id. A retry sends the same value. |
User-Agent |
Conecto-Notify/1.0. |
Compute the HMAC-SHA256 of the raw body with the webhook_secret, as hex, and compare it with the header in constant time. A retry sends the same X-Conecto-Delivery, so drop one you have seen.
Python
import hashlib
import hmac
def is_from_conecto(body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)JavaScript
import { createHmac, timingSafeEqual } from "node:crypto";
export function isFromConecto(body, header, secret) {
const expected = "sha256=" + createHmac("sha256", secret).update(body).digest("hex");
return expected.length === header.length && timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}A message that notify_send posts to a webhook is signed the same way. Its body holds title, message, link, urgent, account and sent_at, with no X-Conecto-Event header.
A bot names its own events with account_emit_event: custom. and a name, like custom.order_shipped, with a title and up to 10 KB of data. They go to every channel subscribed to them and to account_events, where another team's bot can pick them up. Each costs $0.005 per channel it goes to, and sent_by is added with the bot's name and team.
Updated . Every route, field and number here is read from the API's own code.