Universal event-driven omnichannel messaging platform
One API to send, receive, route, and automate messages across WhatsApp, Discord, Slack, Telegram, and more.
CLI-first. Event-driven. Built for AI agents.
Install • Quick Start • AI Agents • CLI • API • SDKs • Development
Think of Omni as a deep-sea octopus. Each channel is a tentacle reaching into a different messaging ecosystem. Events are nerve impulses flowing through NATS JetStream. The core is the brain: identity resolution, event routing, and a unified API that lets you treat every channel as one system.
| 🔌 One API, every channel | Send WhatsApp, Discord, Slack, Telegram, and other channel messages with one interface |
| ⚡ Event-driven | Every action produces an event. Subscribe, replay, automate |
| 🧬 Identity graph | Resolve the same person across channels and instances |
| 🤖 AI-native | Provider bindings, response gates, session strategies, automations, and agent dispatch |
| 🔧 Plugin architecture | Build channels and integrations with the Channel SDK |
| 📦 SDKs | TypeScript SDK, plus generated Python/Go clients for API consumers |
| Channel | Status | Highlights |
|---|---|---|
| WhatsApp (Baileys) | ✅ Stable | QR/phone pairing, media, reactions, groups, contacts, presence |
| WhatsApp Cloud API (Meta) | ✅ Available | Embedded Signup OAuth, templates HSM, webhook (HMAC-SHA256), media, location, reactions |
| Discord | ✅ Stable | Bots, embeds, polls, buttons, threads, slash commands, voice channels |
| Slack | ✅ Available | Socket Mode or HTTP, bot & user (xoxp) token modes, threads, scheduled messages, permalinks, pins, message search |
| Telegram | ✅ Available | Bot API, inline keyboards, groups, channels, threads, polls |
| A2A | ✅ Available | Agent-to-agent channel integrations |
| Gupshup | ✅ Available | Custom Integration webhook support |
| H3rmes (Mutant) | ✅ Available | Brazilian WhatsApp gateway — JWT auth, media, templates, interactive, Pix-ready webhook shapes |
| ASC Brazil | ✅ Available | Brazilian WhatsApp BSP — Cloud API mirror, Meta-format webhooks, typing indicator, templates, interactive |
| ASC Flow | ✅ Available | Brazilian BSP via the ASC platform Flow — REST callbacks, URA buttons/lists, Genesys handoff |
| Twilio WhatsApp | ✅ Available | Twilio sender, webhook, and signature validation support |
Also in the box: Harness (--channel harness), an E2E agent-testing channel that captures outgoing messages verbatim so you can drive and assert on agent conversations from tests, and an internal channel used for agent-to-agent routing.
bun add -g @automagik/omni
omni install # bootstrap the local server/runtime with PM2Migrating from
@omni/cli? Runbun remove -g @omni/clifirst.
curl -fsSL https://raw.githubusercontent.com/automagik-dev/omni/main/install.sh | bashcurl -fsSL https://raw.githubusercontent.com/automagik-dev/omni/main/install.sh | bash -s -- --serverSupported script modes: CLI only (--cli) and Full server (--server).
Non-interactive & manual install
# CLI only
curl -fsSL https://raw.githubusercontent.com/automagik-dev/omni/main/install.sh | bash -s -- --cli
# Full server
curl -fsSL https://raw.githubusercontent.com/automagik-dev/omni/main/install.sh | bash -s -- --server
# Connect the CLI to a remote server after install
omni auth login --api-url https://your-omni-server.example.com --api-key <your-api-key>Manual:
git clone https://github.com/automagik-dev/omni.git && cd omni
make setup # install deps, create .env, start servicesAPI runs at http://localhost:8882 · Swagger docs at /api/v2/docs · API key is printed in the startup banner.
# Authenticate
omni auth login --api-key <your-api-key>
# WhatsApp — scan QR
omni instances create --name "my-whatsapp" --channel whatsapp-baileys
omni instances qr <id> --watch
# Discord — connect bot
omni instances create --name "my-discord" --channel discord --discord-token <discord-bot-token>
omni instances connect <id>
# Telegram — connect bot
omni instances create --name "my-telegram" --channel telegram --telegram-token <telegram-bot-token>
omni instances connect <id>
# Send messages
omni send --to "+15555550100" --text "Hello from the deep 🐙"
# Browse conversations
omni chats list --unread --sort unreadConnect an agent provider and your instances can respond to messages, react to events, and route conversations across channels.
Message received → NATS event → Agent Dispatcher → Provider → Response gate → Channel reply
# Create a provider
omni providers create \
--name "my-agent" \
--schema webhook \
--base-url "https://agent.example.com/webhook" \
--api-key <provider-api-key>
# Bind it to an instance
omni instances update <id> --agent-provider <provider-id>Provider schemas: agno · webhook · openclaw · ag-ui · claude-code · a2a · nats-genie
| Trigger | When | Use Case |
|---|---|---|
| DM | Direct message | Always reply |
| Mention | @bot in group | Respond to mentions |
| Reply | Reply to bot's message | Continue thread |
| Reaction | Emoji on message | Approvals, priority, lightweight workflows |
Built-in: message debouncing · per-user rate limits · access control · response gates · smart chunking · typing presence · cross-channel identity · self-chat detection
Automations & event-driven workflows
# Auto-reply
omni automations create --name "welcome" \
--trigger "message.received" \
--action send_message --action-config '{"text":"Got it! 🐙"}'
# Webhook on connection
omni automations create --name "notify" \
--trigger "instance.connected" \
--action webhook --action-config '{"url":"https://your-app.example.com/hook"}'
# Route important senders to a dedicated agent
omni automations create --name "priority-route" \
--trigger "message.received" \
--condition '{"field":"payload.from","op":"in","value":["+15555550100"]}' \
--action call_agent --agent-id "priority-handler" --provider-id <provider-id>Every state change lands in a total-ordered PostgreSQL journal and is published over NATS. The journal is the source of truth — you can filter it, trace it, wait on it, and consume it durably:
omni events list --type "message.*" --since 2h # trailing-* glob type filters
omni events types --since 7d # what's flowing: volume, schema status, subscribers
omni events stream --pretty --exclude "custom.chat.*" # live tail, one line per event, minus the noise
omni events trace <event-id> # causation chain, rendered as a tree
omni events wait --type "custom.github.*" --timeout 60 # block until a matching event arrives
# Durable named consumers — resumable cursors over the journal
omni events consumers create deploy-watcher --type "custom.github.*"
omni events follow --consumer deploy-watcher # at-least-once, acks as it goesWebhook sources turn external systems into event emitters with configuration only — no code: raw-body signature verification (HMAC-SHA256/SHA1 or token match), per-source idempotency key templates, event-type mapping to custom.<source>.<event>, optional strict schema validation, and heartbeat-based connector liveness (system.connector.stalled / recovered). Working recipes: GitHub and ClickUp.
Event schemas are registered once (omni events schema register) and enforced at webhook ingress and automation emit_event — invalid payloads dead-letter instead of entering the journal.
Agent manifests declare what each agent consumes and publishes (omni agents manifest apply, omni agents graph): the publishes list is enforced at emission time, and accepts compiles into managed routing automations. Automations can also buffer their emitted events and flush them in order only when the whole run succeeds (--transactional-emissions).
Deep dives: event system · durable consumers · agent publish governance
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ WhatsApp │ │ Discord │ │ Slack │ │ Telegram │ Tentacles
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
└────────────────┴────────────────┴────────────────┘
┌─────────▼─────────┐
│ NATS JetStream │ Nerve system
└─────────┬─────────┘
┌─────────▼─────────┐
│ Omni Core │ Brain
└─────────┬─────────┘
┌────────────────────┼────────────────────┐
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ REST API │ │ CLI │ │ Dashboard │ Interfaces
└───────────┘ └───────────┘ └───────────┘
Project structure
packages/
├── core/ # Events, identity, schemas
├── db/ # Drizzle ORM + PostgreSQL
├── api/ # Hono + tRPC + OpenAPI
├── channel-sdk/ # Plugin SDK
├── channel-whatsapp/ # WhatsApp Baileys
├── channel-whatsapp-business/ # WhatsApp Cloud API (Meta)
├── channel-discord/ # Discord
├── channel-slack/ # Slack
├── channel-telegram/ # Telegram
├── channel-gupshup/ # Gupshup
├── channel-hermes/ # H3rmes (Brazilian WhatsApp gateway)
├── channel-twilio-whatsapp/ # Twilio WhatsApp
├── channel-a2a/ # A2A channel
├── channel-asc/ # ASC Brazil (ASCWhats GW, Cloud API mirror)
├── channel-asc-flow/ # ASC platform Flow (Brazilian BSP)
├── channel-harness/ # E2E agent-testing channel
├── channel-internal/ # In-process agent-to-agent routing
├── cli/ # `omni` command
├── media-processing/ # Media sync and extraction
├── plugin-openclaw/ # Omni as a channel inside OpenClaw
├── voice-client/ # Voice transport/codec library (Discord voice)
├── sdk/ # TypeScript SDK
├── sdk-go/ # Go SDK
└── sdk-python/ # Python SDK
apps/
└── ui/ # React + Vite + Tailwind dashboard
Core commands — send, chats, messages, instances, persons
omni send --to "+15555550100" --text "Hello!"
omni send --to "+15555550100" --media ./photo.jpg --caption "Check this out"
omni send --to "+15555550100" --reaction "👍" --message <message-id>
omni send --to "+15555550100" --sticker ./sticker.webp
omni send --to "+15555550100" --contact --name "Example User" --phone "+15555550101"
omni send --to "+15555550100" --location --lat 37.7749 --lng -122.4194 --address "San Francisco"
omni send --to "discord-channel-id" --poll "Favorite color?" --options "Red,Blue,Green"
omni send --to "discord-channel-id" --embed --title "Alert" --description "Server rebooting" --color "#ff0000"
omni send --to "+15555550100" --presence typingomni chats list --unread --sort unread
omni chats messages <chat-id> --limit 20 --rich
omni chats archive <chat-id>
omni chats participants <chat-id> --add "+15555550100"omni messages search "meeting tomorrow" --instance <id> --limit 10
omni messages read --chat <chat-id> --instance <id>omni instances list
omni instances create --name "work-whatsapp" --channel whatsapp-baileys
omni instances qr <id> --watch
omni instances create --name "ops-slack" --channel slack --slack-bot-token <bot-token> --slack-app-token <app-token>
omni instances connect <id>
omni instances sync <id> --type all --depth 30
omni instances contacts <id> --limit 50
omni instances groups <id>Run omni instances --help for the full channel-specific command list.
omni persons search "Example User"
omni persons get <id>
omni persons presence <id>Management — keys, providers, automations, access, webhooks, schedule, tenants
omni keys create --name "agent-key" --scopes "messages:*,instances:read"
omni keys list --status active
omni keys revoke <id> --reason "Compromised"omni providers create --name "webhook-agent" --schema webhook --base-url "https://agent.example.com" --api-key <provider-api-key>
omni providers list
omni providers test <id>Schemas: agno · webhook · openclaw · ag-ui · claude-code · a2a · nats-genie
omni automations create --name "Auto-reply" --trigger "message.received" \
--action send_message --action-config '{"text":"Got it!"}'
omni automations list --enabled
omni automations test <id> --event <event-id> # dry-run against a real journaled event: condition verdicts, no side effectsActions: webhook · send_message · emit_event · log · call_agent
omni access create --type deny --instance <id> --phone "+15555550100" --action block
omni access list --instance <id>
omni access check --instance <id> --user "+15555550100"Modes: disabled · blocklist · allowlist
omni webhooks create --name "github-events" \
--signature-algorithm hmac-sha256 --signature-header "X-Hub-Signature-256" --signature-prefix "sha256=" \
--signature-secret-env GITHUB_WEBHOOK_SECRET \
--event-type-mapping '{"source":"header","header":"X-GitHub-Event"}' \
--idempotency-key-template "github:{headers.x-github-delivery}"
omni webhooks heartbeat github-events # connector liveness check-in
omni webhooks trigger --type "custom.event" --payload '{"key":"value"}'Sources receive on the public POST /api/v2/webhooks/ingress/:source endpoint (signature required). See the GitHub and ClickUp recipes.
omni schedule send <instance> <chat-id> "Stand-up in 10" --at 2h # ISO timestamp or 30m/2h/3d
omni schedule list <instance>
omni schedule cancel <id>Delivery is native where the channel supports it (Slack, text-only); everywhere else a local sweeper sends at the scheduled time.
omni tenants create --slug acme --name "Acme" --max-key-ttl 7776000 --max-key-rate 100 --max-key-budget 1000 --reason "onboarding"
omni tenants keys issue-root <tenant-id> --principal <uuid> --membership <uuid> --role tenant-owner \
--name root --scopes "tenant:*" --expires 2026-12-01 --rate-limit 100 --budget 1000 --reason "bootstrap"
omni multitenancy statusRequires a PLATFORM-class credential and OMNI_MULTITENANCY_ENABLED=true; every command takes an audited --reason. Omni runs single-tenant by default — see platform credential bootstrap.
System — update, status, doctor, auth, config, events, logs
omni update -y # update CLI; restart only services that were already online
omni update -y --no-restart # update CLI only; skip restarts and API-port health check
omni status # verify runtime health
omni doctor # diagnose embedded runtime issues
omni doctor --fix # repair safe runtime drift in-place
omni auth login --api-key <your-api-key> # authenticate
omni config set defaultInstance <id> # CLI settings
omni events list --type "message.*" --since 2h # event history (trailing-* globs)
omni events types # observed event types: volume, schema, subscribers
omni events stream --pretty # live tail, one colored line per event
omni events trace <event-id> # causation chain
omni events wait --type "custom.x.*" --timeout 60 # block until a matching event
omni events schema register <type> --file s.json # register a payload schema
omni events schema validate <type> --file p.json # dry-run a payload, no event emitted
omni events consumers create <name> --type "custom.x.*" # durable cursor
omni events follow --consumer <name> # durable tail
omni events replay --start --since 2024-01-01 # replay events
omni journey show <correlation-id> # per-message latency timeline
omni batch create --instance <id> --type targeted_chat_sync --chat <chat-id>
omni resync --instance <id> # history backfill
omni logs list --level error --limit 50 # server logs
omni dead-letters list --limit 20 # failed eventsomni update can exit non-zero after install only when restart runs (services were already online): in that path it checks API health on the configured API port and fails on restart or health-check errors.
omni doctor --fix never deletes database data. It repairs safe runtime drift such as PM2 state, stored env, and CLI config; destructive cleanup remains manual.
| Base URL | http://localhost:8882/api/v2 |
| Docs | /api/v2/docs (Swagger UI) |
| OpenAPI | /api/v2/openapi.json |
| Auth | x-api-key header |
Main API groups include: /auth, /instances, /messages, /chats, /events (plus /events/schemas, /events/consumers, and per-event /trace), /persons, /access, /settings, /providers, /automations, /webhooks, /scheduled-messages, /keys, /logs, /batch-jobs, /dead-letters, /media, /metrics, /event-ops, /payloads, /agents, /agent-state, /agent-tasks, /conversations, /context, /turns, /trust, /voice, /follow-up, /handoffs, /journeys, /instances/:id/whatsapp-templates, and /platform (multitenancy control plane, flag-gated).
import { createOmniClient } from '@omni/sdk';
const omni = createOmniClient({
baseUrl: 'http://localhost:8882',
apiKey: '<your-api-key>',
});
await omni.messages.send({
instanceId: '<instance-id>',
to: '+15555550100',
text: 'Hello from SDK!',
});Generated Python and Go clients live in packages/sdk-python and packages/sdk-go. They are useful for API consumers, but the TypeScript SDK is the primary maintained SDK for this release.
Regenerate SDKs after API changes:
make sdk-generatemake dev-ui # Dev → http://localhost:5173
make build-ui # Prod → served by API on :8882Pages: Dashboard · Instances · Chats · Chat View · Contacts · Persons · Providers · Automations · Access Rules · Batch Jobs · Dead Letters · Events · Logs · Voices · Settings
🔐 API Keys & Security
API key is generated on first boot and shown once in the startup banner.
omni keys create --name "admin" --scopes "*"
omni keys create --name "reader" --scopes "messages:read,chats:read"
omni keys create --name "channel-only" --scopes "messages:*" --instances "uuid1,uuid2"Scopes use the namespace:action pattern: messages:*, instances:read, * for full access.
Namespaces include: messages, chats, instances, persons, events, access, settings, providers, automations, webhooks, keys, logs, and batch.
⚙️ Configuration & environment
| Variable | Default | Description |
|---|---|---|
API_PORT |
8882 |
API server port |
DATABASE_URL |
postgresql://...localhost:8432/omni |
PostgreSQL |
NATS_URL |
nats://localhost:4222 |
NATS connection |
OMNI_API_KEY |
(auto) | Override primary key |
Set *_MANAGED=false for external services. Full list: .env.example.
| Service | PM2 Name (installer / source checkout) | Port |
|---|---|---|
| API | omni-api / omni-v2-api |
8882 |
| NATS | omni-nats / omni-v2-nats |
4222 |
| PostgreSQL | autopg-server (canonical pgserve, managed by omni install) |
8432 |
make dev # Start all services + API
make dev-ui # Start dashboard dev server
make test # Run test suite
make typecheck # TypeScript checks
make lint # Biome lint
make sdk-generate # Regenerate SDKs from OpenAPIConfiguration and environment
| Variable | Default | Description |
|---|---|---|
API_PORT |
8882 |
API server port |
DATABASE_URL |
postgresql://user:password@localhost:8432/omni |
PostgreSQL connection |
NATS_URL |
nats://localhost:4222 |
NATS connection |
OMNI_API_KEY |
auto-generated | Override primary API key |
Set *_MANAGED=false for external services. Full list in .env.example.
| Service | PM2 Name (installer / source checkout) | Port |
|---|---|---|
| API | omni-api / omni-v2-api |
8882 |
| NATS | omni-nats / omni-v2-nats |
4222 |
| PostgreSQL | autopg-server (canonical pgserve, managed by omni install) |
8432 |
import { BaseChannelPlugin } from '@omni/channel-sdk';
import type { InstanceConfig, OutgoingMessage } from '@omni/channel-sdk';
export class MyPlugin extends BaseChannelPlugin {
readonly id = 'my-channel';
readonly name = 'My Channel';
readonly version = '1.0.0';
readonly capabilities = { /* ... */ };
async connect(instanceId: string, config: InstanceConfig) { /* ... */ }
async disconnect(instanceId: string) { /* ... */ }
async sendMessage(instanceId: string, message: OutgoingMessage) { /* ... */ }
}| Runtime | Bun |
| HTTP | Hono |
| API | tRPC + OpenAPI |
| DB | PostgreSQL + Drizzle |
| Events | NATS JetStream |
| Validation | Zod |
| Frontend | React + Vite + Tailwind |
| Monorepo | Turborepo |
| Linter | Biome |
Apache-2.0 — do whatever you want, just don't blame the octopus. 🐙