Skip to content

Integrations

Node.js SDK

The official TypeScript and JavaScript client: typed requests, automatic idempotency keys and retries, typed errors, pagination and webhook verification.

Install#

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, …).

send.ts
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#

PropertyMethods
client.messagessend / create, createBatch, get, list, listEvents, waitFor
client.channelslist, get, create, update, delete, listTemplates
client.webhookEndpointslist, get, create, update, delete, rollSecret, test
client.contactslist, get, create, upsert, update, delete, tag, listTags, lists (listLists, createList, addListMembers, …) and segments (listSegments, getSegment, previewSegment)
client.campaignslist, get, create, launch, pause, resume, cancel, listRecipients
client.balance, client.pricing, client.usageget, 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:

Push an automation event
// 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.

Pagination
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#

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
  }
}
ClassHTTPtype
InvalidRequestError400invalid_request_error
AuthenticationError401authentication_error
BillingError402billing_error
PermissionError403permission_error
NotFoundError404not_found_error
ConflictError409conflict_error
ChannelError422channel_error
RateLimitError429rate_limit_error (retryAfter in seconds)
ServerError5xxapi_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.

Verify a webhook
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.

    Loading