Inbound sources
Give a project an e-mail address, a signed webhook URL, a text channel or a Google Pub/Sub delivery; every message is stored before it is acknowledged, then delivered once to each agent you subscribe.
Updated Sep 15, 2026
An inbound source is one place messages arrive: an address on our inbound domain, a webhook URL with its own secret, a text channel (Slack workspace, Telegram bot, SMS or WhatsApp number) or a Google Pub/Sub subscription — pushed to us by your own service account, or pulled. It lives on your project's Connections page like any other connection, and zero or more agents subscribe to it. Every message is written to the project's inbound ledger before the sender gets its acknowledgement, so nothing is lost when an agent is paused, out of credits, or mid-deploy.
Create one
Connections → New → Inbound offers four cards:
| Card | What you get |
|---|---|
| E-mail address | <name>-<suffix>@inbound.connect0.ai; forward or point a mailbox at it |
| Webhook endpoint | https://webhooks.connect0.ai/inbound/<random path> and a signing secret shown once |
| Text messages | Slack (installed through the workspace sign-in), a Telegram bot (username + secret token), SMS / WhatsApp (number + auth token) |
| Pub/Sub | Push (recommended): an endpoint URL your subscription posts to, signed by your own service account. Pull: the subscription name and one grant naming our puller (below) |
Each card creates the source and opens its page, where you subscribe agents, pause delivery, rotate the secret, or remove it.
Subscribe agents
A source with no subscribers still stores everything; the events show as unrouted under Project → Inbound until an agent subscribes. A subscription is one agent plus two optional settings:
- Filter — a field, an operator (
eq,in,prefix,matches) and a value; only matching messages reach that agent.type eq issue.createdon a Linear webhook,subject prefix [urgent]on an inbox. - Thread policy — where the message lands: per kind (e-mail: one
sticky thread per sender; Slack: per channel; Telegram / SMS: per chat),
sticky key (a payload field such as
issue.idkeeps one thread per issue), or new each (a fresh thread per message — the default for webhooks and Pub/Sub).
Two agents subscribed to one inbox each keep their own threads. The same subscriptions appear on the agent's Invocations tab.
How a message is verified
Every transport is verified before anything is stored; a failed check is
answered 401 and never written.
| Transport | Header / scheme |
|---|---|
| Your own webhook (generic) | x-connect0-signature: t=<unix seconds>,v1=<hex> — HMAC-SHA256 of ${t}.${body} with the source secret; t must be within 300 s. The same format connect0 signs its own outbound webhooks with. The older sha256=<hex> form (with x-connect0-timestamp, or over the body alone) is accepted until 2026-12-31 and refused from then on with webhook_signature_legacy_format |
| GitHub | x-hub-signature-256 over the body |
| Stripe | stripe-signature (t=…,v1=…) over ${t}.${body}, 300 s window |
| Linear | linear-signature over the body |
| Svix-delivered providers (incl. our inbound e-mail) | svix-id, svix-timestamp, svix-signature |
| Slack | x-slack-request-timestamp + x-slack-signature (v0) |
| Telegram | x-telegram-bot-api-secret-token equal to the bot's secret token |
| SMS / WhatsApp | x-twilio-signature over the URL and form fields |
| Pub/Sub push | a Google OIDC bearer token signed by the service account you named, audience = the endpoint URL |
Rotating a secret keeps the previous one valid for 24 hours so a sender can switch without a gap.
Deduplication and size
The sender's own event id is the deduplication key (the GitHub delivery
id, the Stripe event id, the Svix message id, the Slack event id, the
Telegram update id, the Pub/Sub message id); when a transport has none,
send x-connect0-idempotency-key, or the timestamped body is hashed. A
retry with the same id is acknowledged and stored once.
A message body is stored up to 256 KB; a larger body is kept
truncated and marked as such. Each source accepts a bounded number of
messages per minute (10 by default, up to 60); a sender that exceeds it
receives 429 and retries — nothing is dropped.
The ledger, retention and re-delivery
Project → Inbound lists every stored message with its status (stored, delivered, unrouted, deferred, failed, expired), which agents it reached and the runs it started. Delivery pauses — and resumes on its own — when the account has no credits or a source is paused; a source's messages queue in order.
Who wrote. An e-mail's sender is kept as an identity — the address, and the name from the From header once it has been seen — so the list shows Jane Doe rather than an address, a count when the same person has written before, and every message from one sender is one click away (Everything from this sender on a message). The name and count belong to your account alone: another account hearing from the same address keeps its own. Slack, SMS and webhook senders are not identities yet.
The stored body is nulled after the account's retention window (90 days by default; an owner can shorten or lengthen it); the row, its deliveries and the run links remain so the history stays readable.
Re-deliver on a message creates a fresh delivery for the agents that never received it or whose delivery failed — after fixing a filter, an agent, or an outage. It asks for a reason and is written to the audit log. Agents cannot trigger a re-delivery; only a person can.
What the agent is told
A thread that reacts to an inbound message is marked as one — the thread shows E-mail from Jane Doe next to the agent's name, linking to the source it came from. Every such run starts with a short platform baseline the agent cannot lose: that the message came from outside, who sent it (when the address is known), how the answer goes out (an e-mail is replied to; a webhook delivery is acted on, not answered), and the rules — answer in the sender's language, never answer automated mail, never reveal internal identifiers or other customers, ask one question rather than guess, say so and stop when the request is outside the agent's purpose, never claim an action that was not taken.
What this inbox is for is yours to write: a skill on the agent whose
front matter says handles: [inbound] — the receptionist's script, the
support triage flow. It takes precedence over the baseline. Until an
agent has one, the source page, the agent's Invocations tab and the
thread's ops view say so.
Pub/Sub: push or pull
Push keeps every grant inside your project — nothing names us. Your service account signs each delivery; Pub/Sub needs permission to mint tokens as it, once, and the subscription points at the endpoint URL the source page shows:
gcloud iam service-accounts add-iam-policy-binding <your-push-service-account> \
--member=serviceAccount:service-<project-number>@gcp-sa-pubsub.iam.gserviceaccount.com \
--role=roles/iam.serviceAccountTokenCreator
gcloud pubsub subscriptions create <name> --topic=<topic> \
--push-endpoint=<the endpoint URL on the source page> \
--push-auth-service-account=<your-push-service-account>
Leave the audience at its default (the endpoint URL). If a delivery is refused, the source page says why in one sentence: no token, signed by a different account (it names which), or an audience that is not the URL.
Pull is the alternative: we pull from your subscription with our worker's service account, so the subscription needs the subscriber role once (the exact command, with the account, is on the card and the source page):
gcloud pubsub subscriptions add-iam-policy-binding <subscription> \
--member=serviceAccount:<the service account shown on the source page> \
--role=roles/pubsub.subscriber
Either way a message is acknowledged only after it is stored, and a redelivery is deduplicated on its message id. To move a subscription from pull to push: create the push source, remove the pull source, then re-point the subscription — a message that reaches both while both exist is stored twice; one that arrives in between is retained by Pub/Sub and redelivered.
Next
- Create an agent to subscribe, then watch its first inbound run from the source page.
- Running agents for the other ways a run starts.
What this does not do
- Inbound only. Replies and outbound messages are the agent's own tools, not part of a source.
- No ordering across sources. Within one source, deliveries follow arrival order; two sources are independent.
- No exactly-once towards the sender. We acknowledge after our own commit; a sender that retries is deduplicated by its event id, never delivered twice.
- Retention nulls the stored message after the account's retention window (90 days by default); the ledger row, its deliveries and run links stay.
- Unsigned webhooks are not accepted on the public endpoint. Every transport verifies a signature, a token or a Google identity.
- E-mail attachments become files in the project media library (documents up to 50 MB, images, audio and video up to 500 MB); other types are noted in the thread but not stored.
Ask Zero
Ask a question about connect0 and get an answer grounded in the docs, with links to the sources. Signed in? Zero answers with your account in mind.