Integrations
Node.js SDK
The official TypeScript and JavaScript client: typed requests, automatic idempotency keys and retries, typed errors, pagination and webhook verification.
Install#
npm install @omnimessage/sdk- Typed from the OpenAPI document. No runtime dependencies.
- Uses the platform
fetch: Node.js 18 and later, Bun, Deno, Cloudflare Workers, Vercel Edge. - ESM and CommonJS builds:
const { OmniMessage } = require('@omnimessage/sdk');.
Quick start#
Create an API key in the console. With an om_test_ key nothing is delivered and nothing is billed, and every account has a sandbox channel per type (ch_test_whatsapp, ch_test_sms, …).
import { OmniMessage } from '@omnimessage/sdk';
const client = new OmniMessage({ apiKey: 'om_test_xxxxxxxxxxxxxxxxxxxxxxxx' }); // or set OMNIMESSAGE_API_KEY
const message = await client.messages.send({
channel: 'ch_test_whatsapp',
to: '+971501234567',
type: 'text',
text: { body: 'Your code is 482910' },
reference: 'order-1042',
});
console.log(message.id, message.status); // msg_..., "queued"Resources#
| Property | Methods |
|---|---|
client.messages | send / create, createBatch, get, list, listEvents, waitFor |
client.channels | list, get, create, update, delete, listTemplates |
client.webhookEndpoints | list, get, create, update, delete, rollSecret, test |
client.contacts | list, get, create, upsert, update, delete, tag, listTags, lists (listLists, createList, addListMembers, …) and segments (listSegments, getSegment, previewSegment) |
client.campaigns | list, get, create, launch, pause, resume, cancel, listRecipients |
client.balance, client.pricing, client.usage | get, also client.billing.getBalance() and friends |
client.me() | The account and key behind the client; needs no scope |
Every method takes an optional last argument { idempotencyKey, maxRetries, timeoutMs, signal, headers }. client.request({ method, path, query, body }) reaches endpoints that the installed version does not have a method for yet, such as automation events:
// Endpoints newer than the generated resources are reached with client.request().
const receipt = await client.request({
method: 'POST',
path: '/automation_events',
body: {
source: 'src_9Kd2mQ5vB8cX1zL0pK3j', // with an API key that has events:write
id: 'shop:order:5012:order.paid:1',
type: 'order.paid',
occurred_at: new Date().toISOString(),
customer: { first_name: 'Layla', phone: '+971501234567' },
order: { id: '5012', number: '1042' },
},
});Pagination#
List methods return a page when awaited and iterate across pages with for await.
const page = await client.messages.list({ status: 'failed', limit: 50 });
console.log(page.data.length, page.has_more);
for await (const message of client.messages.list({ status: 'failed' })) {
console.log(message.id);
}
const firstHundred = await client.messages.list().all(100);Idempotency and retries#
Every POST carries an Idempotency-Key. The SDK generates one per call and reuses it for the retries of that call, so a retried send cannot produce two messages. Pass your own key to make a send safe across process restarts: client.messages.send(body, { idempotencyKey: 'order-1042-shipped' }).
Connection errors, 408, 429, 5xx and 409 idempotency_key_in_use are retried: 2 retries by default, with exponential backoff and jitter, or the Retry-After header when present, capped at 60 seconds. Configure with new OmniMessage({ maxRetries, timeoutMs }) or per call.
Errors#
import { APIError, BillingError } from '@omnimessage/sdk';
try {
await client.messages.send(body);
} catch (error) {
if (error instanceof BillingError) {
// 402 insufficient_balance: no package credits and the wallet is too low
} else if (error instanceof APIError) {
console.error(error.status, error.type, error.code, error.param, error.requestId, error.docUrl);
} else {
throw error; // ConnectionError, TimeoutError
}
}| Class | HTTP | type |
|---|---|---|
InvalidRequestError | 400 | invalid_request_error |
AuthenticationError | 401 | authentication_error |
BillingError | 402 | billing_error |
PermissionError | 403 | permission_error |
NotFoundError | 404 | not_found_error |
ConflictError | 409 | conflict_error |
ChannelError | 422 | channel_error |
RateLimitError | 429 | rate_limit_error (retryAfter in seconds) |
ServerError | 5xx | api_error |
responseMeta(result) returns the status, headers, request ID, rate-limit state and whether the response was an idempotent replay. Error codes are explained under Errors.
Webhooks#
constructEvent verifies the OmniMessage-Signature header against the raw body with Web Crypto, so it also works at the edge. Signatures older than 5 minutes are rejected (toleranceSeconds). Retries keep the event ID: deduplicate on event.id. See Webhooks.
import { constructEvent } from '@omnimessage/sdk';
const event = await constructEvent({
payload: rawBody, // string or Uint8Array, exactly as received
signature: request.headers.get('OmniMessage-Signature'),
secret: process.env.OMNIMESSAGE_WEBHOOK_SECRET, // whsec_..., shown once when the endpoint is created
});
if (event.type === 'message.delivered') {
// event.data.object is the message
}Test mode#
With an om_test_ key, recipients ending in 0000 fail with provider_error, 0001 stay sent, 0002 are also read; anything else goes sent and then delivered within about two seconds. See Test mode.