Every channel. One API.
Built to be plugged into.
Send WhatsApp, SMS and email from your product with one call. Get delivery status and customer replies back on your webhook. This page is the complete integration reference.
What Omni does with your call
You never talk to Meta, SMS vendors or SMTP. Your app talks to one API; Omni handles channels, delivery and conversations, and tells you what happened.
whatsapp → sms → email. Retries are deduplicated — never a double send.Pick your surface — each documented below
REST API
Scoped API keys. Send notifications, read conversations, tickets and contacts.
LIVEWebhooks
Omni calls you back: delivery, failures, replies. HMAC-signed, retried.
LIVELive-chat widget
Embed chat on your site — visitors write, your team answers in Omni.
LIVEEvents API
Push business events; flows open conversations and tickets.
SOONSDK
Typed wrapper over the REST API once the surface stabilizes.
SOONREST API — full reference
Authentication & API keys
Create a key in Settings → Developer. Keys are shown once — store them in your secret manager. Every key carries scopes; request only what you need. Pass the key as a Bearer token on every call:
Authorization: Bearer omni_sk_your_key_here Content-Type: application/json
| Scope | Grants |
|---|---|
notify:send | Send templated notifications via POST /api/v1/notify |
messages:read · messages:write | List and read conversations · ingest and reply |
tickets:read · tickets:write | List, create and update tickets |
contacts:read · contacts:write | Customer profiles |
analytics:read | Overview and agent-performance metrics |
events:write | Business events API (coming soon) |
X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset. On 429, back off until reset. Sandbox keys return mock data and never touch production — use them in CI.Send a notification — POST /api/v1/notify
The primary endpoint for product integrations. Requires the notify:send scope.
| Field | Type | Description | |
|---|---|---|---|
idempotencyKey | REQUIRED | string | Your unique key per logical message, e.g. rent_due:acct1:tenant9:2026-08. Repeats within 30 days return the original result with "deduped": true — retry as often as you like. |
contact.externalId | REQUIRED | string | Your stable ID for this person: yourapp:<scope>:<id>. Omni upserts a customer profile against it — you never store Omni IDs. |
contact.name | REQUIRED | string | Display name. |
contact.phone | optional | string | E.164 (+91…). Needed for the whatsapp / sms channels. |
contact.email | optional | string | Needed for the email channel. |
channels | REQUIRED | string[] | Fallback chain, tried in order: any of whatsapp, sms, email. The response's channelUsed tells you which one won. |
template | REQUIRED | string | A name from the template catalog. Free-form text is never accepted. |
vars | REQUIRED | object | String values for the template's variables. |
meta | optional | object | Anything you want echoed in webhook events (source, ids). |
curl -X POST https://api.vayusetu.com/api/v1/notify \ -H "Authorization: Bearer omni_sk_YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "idempotencyKey": "rent_due:acct1:tenant9:2026-08", "contact": { "externalId": "yourapp:acct1:tenant9", "name": "Ravi Kumar", "phone": "+919876543210", "email": "ravi@example.com" }, "channels": ["whatsapp", "email"], "template": "rent_due", "vars": { "tenantName": "Ravi", "amount": "12000", "month": "August 2026", "dueDate": "2026-08-05", "propertyName": "Sunrise A-101", "upiLink": "upi://pay?pa=owner@upi" } }'
# 200 — first call { "ok": true, "messageId": "wamid.HBg…", "channelUsed": "whatsapp", "deduped": false } # 200 — same idempotencyKey again (retry-safe) { "ok": true, "messageId": "wamid.HBg…", "channelUsed": "whatsapp", "deduped": true }
# every error has the same shape { "ok": false, "error": { "code": "TEMPLATE_UNKNOWN", "message": "Unknown template 'rent_du'", "retryable": false } } # codes: 401 UNAUTHORIZED · 403 missing scope · 422 TEMPLATE_UNKNOWN / INVALID_CONTACT # 429 RATE_LIMITED (retry after reset) · 502 NO_CHANNEL_AVAILABLE (retry later)
Other endpoints
The same key (with matching scopes) can read and write the rest of the platform:
| Method | Path | Purpose | Scope |
|---|---|---|---|
| GET / POST | /api/messages | List conversations · ingest a message | messages:* |
| POST | /api/messages/:id/reply | Reply to a conversation | messages:write |
| GET / POST | /api/tickets | List · create tickets | tickets:* |
| GET / PATCH | /api/customers/:id | Read · update customer profiles | contacts:* |
| GET | /api/analytics/overview | Metrics overview | analytics:read |
The full, always-current endpoint list lives in-app: Settings → Developer → Docs.
Webhooks — events pushed to you
Register an HTTPS endpoint and Omni POSTs events to it as they happen. Register in Settings → Webhooks or via POST /api/webhooks/endpoints; you receive a signing secret at registration.
Event types
| Event | Fires when | Key payload fields |
|---|---|---|
message.delivered | A notification reached the customer | messageId · contact.externalId · channelUsed · timestamp |
message.failed | All delivery attempts failed | messageId · contact.externalId · error |
message.read | Customer read it (channels that report it) | messageId · timestamp |
conversation.replied | Customer wrote back | conversationId · contact.externalId · preview · conversationUrl |
conversation.closed | Conversation resolved in the inbox | conversationId · resolution |
Verify the signature — always
Every delivery is signed: X-Omni-Signature: sha256=<HMAC-SHA256 of the raw body> with your endpoint secret. Reject anything that doesn't verify.
const crypto = require('node:crypto'); function verifyOmni(rawBody, header, secret) { const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex'); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header)); }
import hmac, hashlib def verify_omni(raw_body: bytes, header: str, secret: str) -> bool: expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, header)
POST https://yourapp.example/webhooks/omni
X-Omni-Signature: sha256=8f3a…
{
"event": "conversation.replied",
"contact": { "externalId": "yourapp:acct1:tenant9" },
"preview": "Paid just now via GPay 🙏",
"conversationUrl": "https://vayusetu.com/inbox/c/8412",
"timestamp": "2026-08-05T09:12:44Z"
}
Live-chat widget — one script tag
Put Omni's chat on any website. Visitors write from your page; your team answers from the Omni inbox alongside every other channel. Create the widget under Settings → Channels → Live Chat to get your widget ID and snippet:
<script> window.omniWidget = { id: "wgt_your_widget_id", accent: "#22d3c5" }; </script> <script async src="https://vayusetu.com/widget.js"></script>
Configure colors, greeting, office hours and pre-chat form in the same settings screen — no redeploy of your site needed.
Events API — coming soon
Push business events (repair_filed, order_placed, moveout_requested…) and let Omni automation flows decide what happens: open a conversation, create a ticket, notify an agent, or just log it on the customer's timeline. Same auth as the REST API, scope events:write.
POST /api/v1/events { "idempotencyKey": "evt:acct1:repair:789", "type": "repair_filed", "contact": { "externalId": "yourapp:acct1:tenant9", "name": "Ravi" }, "data": { "issue": "Tap leaking", "ticketId": "789" }, "links": { "sourceUrl": "https://yourapp.example/maintenance/789" } }
SDK — coming soon
A typed npm package wrapping this API — types for every template and event, automatic retries with backoff, and idempotency-key helpers, so the guarantees above become one-liners:
import { Omni } from '@omni/sdk'; const omni = new Omni({ apiKey: process.env.OMNI_API_KEY }); await omni.notify.send({ idempotencyKey: `rent_due:\${acct}:\${tenant}:\${month}`, contact: { externalId: `yourapp:\${acct}:\${tenant}`, name, phone, email }, channels: ['whatsapp', 'email'], template: 'rent_due', vars: { tenantName, amount, month, dueDate, propertyName, upiLink }, });
notify, one webhook handler) is the recommended pattern today. See the REST API reference above; the whole integration is typically under a hundred lines.Template catalog
Every message is a named template with typed vars. Unknown templates are rejected with 422 — raw text is never sent. Need a new one? It's a catalog entry plus a WhatsApp template approval.
| Template | Typical trigger | Vars |
|---|---|---|
rent_due | Invoice generated | tenantName · amount · month · dueDate · propertyName · upiLink |
rent_overdue | Balance past due date | + daysOverdue · lateFee |
payment_claim_received | Customer claims payment | tenantName · amount · month |
payment_confirmed | Owner confirms | + receiptLink |
payment_rejected | Owner rejects | + reason |
agreement_sent / agreement_signed | E-sign lifecycle | tenantName · propertyName · agreementLink |
repair_filed / repair_updated | Maintenance lifecycle | issue · ticketId · status |
lease_expiring | Lease ends ≤ 30 days | tenantName · propertyName · leaseEnd |
moveout_requested | Customer requests move-out | tenantName · propertyName · requestedDate |
Error handling
| HTTP | Code | Meaning | Retry? |
|---|---|---|---|
| 401 | UNAUTHORIZED | Missing, invalid or revoked API key | No — fix the key |
| 403 | UNAUTHORIZED | Key lacks the required scope | No — reissue with scope |
| 422 | TEMPLATE_UNKNOWN | Template not in the catalog | No |
| 422 | INVALID_CONTACT | No usable phone/email for the requested channels | No |
| 429 | RATE_LIMITED | Hourly key limit exceeded | Yes — after X-RateLimit-Reset |
| 502 | NO_CHANNEL_AVAILABLE | Every channel in the chain failed | Yes — with backoff; idempotency makes it safe |