TypeScript SDKpreview
Use @ody/sdk from Node.js, Deno, Bun or the browser: setup, pagination, errors, idempotency and webhook verification.
@ody/sdk isn't on npm yet; see availability. The API it wraps is stable and live.
#Setup
npm install @ody/sdk # once publishedimport { Ody } from "@ody/sdk";
const ody = new Ody(process.env.ODY_API_KEY!);
// Options: new Ody(key, { baseUrl: "https://api.ody.co" })The client is ESM, has no dependencies, and runs anywhere fetch exists (Node.js 18+, Deno, Bun, Cloudflare Workers, browsers). Never ship a live key to a browser. Keys act as a workspace member, so call Ody from your server.
#Usage
// Contacts
const contact = await ody.contacts.create({ firstName: "Ada", phone: "+15125550123", tags: ["vip"] });
for await (const c of ody.contacts.listAll({ q: "ada" })) console.log(c.id);
// Conversations
const { conversations } = await ody.conversations.list({ status: "open", limit: 25 });
const { conversation, activities } = await ody.conversations.get(conversations[0].id);
await ody.conversations.setStatus(conversation.id, "done");
// Messages
const sent = await ody.messages.send({ to: "+15125550123", body: "Hi!", idempotencyKey: "welcome-ada" });
const hits = await ody.messages.search("refund", { limit: 10 });
// Calls
const calls = await ody.calls.list({ limit: 20 });
await ody.calls.place({ to: "+15125550123" });
// Numbers, 10DLC, agent
const available = await ody.numbers.searchAvailable({ startsWith: "512" });
await ody.numbers.buy({ phoneNumber: available[0].phone_number, idempotencyKey: "main-line" });
const status = await ody.messaging.status();
await ody.agent.update({ greeting: "Thanks for calling!" });
await ody.agent.publish();#Errors
Every non-2xx response throws OdyError:
import { OdyError } from "@ody/sdk";
try {
await ody.messages.send({ to: "+15125550123", body: "Hi" });
} catch (err) {
if (err instanceof OdyError) {
switch (err.code) {
case "failed_precondition": /* e.g. 10DLC not approved yet: show err.message */ break;
case "resource_exhausted": await sleep((err.retryAfter ?? 1) * 1000); break;
case "unauthenticated": /* key revoked */ break;
}
console.error(err.status, err.code, err.message, err.requestId);
} else {
throw err; // network error
}
}The codes are listed in Errors.
#Webhook verification
import { verifyWebhookSignature } from "@ody/sdk";
const ok = verifyWebhookSignature(secret, headers, rawBody); // rawBody: string | Uint8ArrayIt checks the v1 HMAC-SHA256 signature in constant time and rejects timestamps more than 5 minutes off. It uses node:crypto, so it's server-side only. Full examples are in Verifying signatures.
#Webhooks
parseWebhookEvent verifies and parses in one step, and returns a WebhookEvent whose data is typed by event.type. It throws WebhookVerificationError on a bad or stale signature, so a handler is a try around one call:
import express from "express";
import { parseWebhookEvent, WebhookVerificationError } from "@ody/sdk";
app.post("/ody-webhook", express.raw({ type: "application/json" }), (req, res) => {
let event;
try {
event = parseWebhookEvent(process.env.ODY_WEBHOOK_SECRET!, req.headers, req.body);
} catch (err) {
if (err instanceof WebhookVerificationError) return res.status(401).end();
throw err;
}
switch (event.type) {
case "call.ringing":
screenPop(event.data.from, event.data.callerName); // typed as CallRingingData
break;
case "call.answered":
if (event.data.answeredBy === "member") assign(event.data.memberUid);
break;
case "contact.updated":
case "contact.deleted":
void syncContact(event.data.contactId); // ContactEventData
break;
}
res.status(200).end();
});WEBHOOK_EVENT_TYPES is the catalog as a readonly tuple (WebhookEventType is its union), and isKnownWebhookEventType(type) tells you whether a delivery's type is one this SDK version knows, so a subscription to ["*"] can log new types instead of crashing. Each event's fields are described in Event types.
#Types
Everything is exported: Contact, ContactSummary, ContactInput, ConversationSummary, Activity, PhoneNumber, MessagingStatus, Agent, AgentInput, Webhook, WebhookDelivery, OdyErrorCode, and for webhooks WebhookEvent, WebhookEnvelope, WebhookEventDataMap, CallRingingData, CallAnsweredData, CallForwardedData, ContactEventData and more. They mirror the OpenAPI spec and the event catalog.