Concepts
How routes, sources, topics, destinations, and connections fit together, and how routing rules decide which events get delivered.
-
Source
Stripe and other providers, verified by signature
-
Route
Your es_ ingest token
-
Topic
Minimum level + event pattern
-
Destination
Slack · Discord · Telegram · Webhook · Pushover · Web Push
Slack, Discord, and Telegram destinations are backed by a connection — the OAuth install that lets EventSend post on your behalf. Webhook, Pushover and Web Push destinations carry their own credentials and need none.
EventSend has five moving parts. Events come in through a route, either from your own code or from a source. Topics decide which events continue, and to which destinations. Some destinations are backed by a connection.
Route
A route is an ingest credential. It owns the es_ token you send as Authorization: Bearer, and
everything you send through it belongs to that route. If the token leaks, Regenerate Token on
the route issues a new one and retires the old one on the spot. Most teams start with one route per
application or per environment — for example a production and a staging route — so you can
filter and disable them independently.
A route can be paused with active, and put in debug mode, which validates events and then
drops them instead of enqueuing them for delivery — nothing is recorded and no quota is spent. Debug
mode is useful while you are wiring up a new integration and don't want to spam a channel. A debug
route answers 200 with {"accepted": false, "debug": true} rather than the 202 a live route
returns, so a client that checks the status code can tell the difference.
Source
A source ingests events from a third-party provider instead of from your code. It has its own token
(starting with src_) and its own signing secret, which is the provider's — EventSend verifies each
incoming request's signature against it before doing anything else.
A source maps the provider's events onto EventSend events using an opinionated default mapping, so you get useful messages with zero code. See Stripe for the mapping table and setup steps. The Free plan includes one source; paid plans have no limit.
Topic
A topic connects routes to destinations, and carries the routing rules. An event that arrives on a route flows through every topic that route is connected to; for each topic whose rules the event satisfies, EventSend creates a delivery for each of that topic's destinations.
Because topics are many-to-many on both sides, one route can feed several topics with different
rules — "everything to the archive webhook, only critical to Telegram" — without sending the event
more than once.
Destination
A destination is where a message is delivered. There are six types, in two groups.
Team destinations reach a channel everyone in the organization can see: Slack, Discord, Telegram, and webhook (your own HTTPS endpoint).
Personal destinations reach one person's own devices: Pushover (using your own Pushover application token, so your allowance is never pooled with anyone else's) and Web Push (browser push, including an installed mobile web app). You create these for yourself under Destinations → Push notifications. Everyone in the organization can see that the destination exists and who it belongs to — it can be attached to topics like any other — but only its owner can change its settings, manage its devices, or resend through it.
Each destination has its own retry setting (up to four retries on a schedule spanning about fifteen minutes — see Delivery and retries) and its own delivery log, so a broken endpoint on one destination never blocks the others.
The Free plan includes three team destinations; paid plans have no limit. Personal destinations never count toward it, and the limit applies when you add a destination — nothing already delivering is ever paused by it.
Connection
Slack, Discord, and Telegram destinations are backed by a connection — the install that grants EventSend permission to post into your workspace or chat. For Slack and Discord that's an OAuth install; for Telegram it's your organization's own private bot, created for you in about a minute. Create the connection once, then point as many destinations at it as you like.
Webhook and personal push destinations need no connection: a webhook carries its own URL, and a push destination is authorized by the device or Pushover token you register on it.
User
An event can carry a user_id: your own stable identifier for the customer the event is about — an
account id, or your Stripe customer id. It is not an EventSend user, and it should not be an email
(emails change; put them in payload). The value is matched exactly, so send the same one from every
system that reports on that customer: the events page filters by it, and the MCP server's
get_user_timeline tool reads every event carrying it back in order, so an assistant can answer "what
did cus_9Xk2 do before cancelling?". Stripe sources set it to the Stripe customer id for you.
The other optional field, occurred_at, is when the thing actually happened, as an ISO-8601
timestamp; it defaults to the moment EventSend received the event, and timelines are ordered by it.
Routing rules
A topic's minimum-level rule and event-name rule are combined with AND: an event must satisfy both to be delivered.
Minimum level
Levels are ordered from least to most severe:
default → success → warning → error → critical
A minimum level of warning accepts warning, error, and critical events. The default setting
accepts every level.
Event name pattern
Patterns match the complete event name without regard to letter case. * is the only special
character and matches any sequence, including an empty sequence. Every other character is matched
literally.
| Pattern | Matches | Does not match |
|---|---|---|
payment-* |
payment-received, payment-failed |
user-created |
payment.* |
payment.succeeded, payment.refund.created |
payment-failed |
*.failed |
invoice.failed, deploy.failed |
failed.retry |
Leave the pattern blank to accept every event name. The topic form includes a live preview for testing a sample event name before saving.
Events that match no routing path
EventSend accepts and records an event even when no active topic-to-destination path matches it. The
event is marked dropped, remains visible in event history, has no delivery attempts, and counts
once toward your organization's event usage. This keeps ingestion history and billing independent
from fan-out size — sending the same event to five destinations still costs one event.
Editing a route, topic, destination, or connection invalidates your organization's routing snapshot. The next event uses the updated rules without requiring a restart; a 120-second snapshot expiry is the safety backstop for a missed cross-node invalidation message.
Event history and search
Every accepted event stays visible until your plan's retention window ends (7 days on Free, 30 on Starter, 60 on Growth, 90 on Scale). The events page reads that history newest-first by default, with an oldest-first toggle, and scrolling loads the next page from where the last one stopped rather than by page number — so new events arriving while you read never shift a row onto a page you have already passed.
The search box matches anywhere inside the event name or the message, case-insensitively, over
your whole retained history. Name and message are searched as a single value, so deploy.failed Rollback finds an event named deploy.failed whose message begins "Rollback". Search combines with
every other filter, and %, _ and \ are matched as ordinary text rather than as wildcards — so
signed_up finds user.signed_up and 50% finds a message about 50%, not one about 500.
Search needs at least three characters, one of them a letter, digit or mark, so deploy,
a-b-c, v1.2, signed_up and 404 all qualify, while ab, ... and 🚀 do not — the page
says so rather than showing an empty feed. Payloads are not searched; use the level, topic, route,
and date filters to narrow first.
A filtered or searched page is answered from the 50,000 most recent events past the point you have scrolled to. When fewer than a page of matches are found there, the page shows what it found and scrolling continues the search further back on its own — the feed says "Searching older events" while it does — so a rare term in a long history arrives in steps rather than in one long wait. The unfiltered feed reads straight from its index and is not windowed.