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.

On this page
  1. What an event is
  2. Reading events
  3. Sending events to a channel
  4. Webhooks
  5. Custom events

What an event is

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_created.
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.

Reading events

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.

account_events, the arguments
{
  "after": 1520,
  "kinds": ["calendar.*", "forms.*", "esign.*"]
}
account_events, the result
{
  "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.

Sending events to a channel

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.

  1. notify_add_channel saves 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.
  2. notify_subscribe sends chosen events to it, for $0.001: one kind, every event of an area like hiring.*, the events bots send like custom.*, or * for all of them, up to 30 names. It replaces the channel's list.
  3. notify_unsubscribe stops them. The channel stays for notify_send.
notify_subscribe, the arguments
{
  "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.

Webhooks

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.

The body of a delivery
{
  "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_created.
X-Conecto-Delivery evt_ and the event's event_id. A retry sends the same value.
User-Agent Conecto-Notify/1.0.

Checking the signature

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.

Custom events

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.