TypeScript SDK
Use the official Sendable TypeScript SDK with session-scoped and account-scoped API keys.
Installation
npm install @sendable-dev/sdkCreate a Client
The SDK uses one Sendable client with resource namespaces like messages, sessions, and webhooks.
import { Sendable } from '@sendable-dev/sdk'
export const sendable = new Sendable({
apiKey: process.env.SENDABLE_API_KEY!,
apiKeyScope: 'session',
baseUrl: process.env.SENDABLE_API_URL,
webhookSecret: process.env.SENDABLE_WEBHOOK_SECRET,
})Configuration
| Option | Required | Description |
|---|---|---|
apiKey | Yes | Your Sendable API key |
apiKeyScope | No | 'session' or 'account'; recommended so the SDK can fail fast on invalid operations |
baseUrl | No | API base URL, defaults to https://api.sendable.dev |
webhookSecret | No | Svix whsec_... signing secret used by webhooks verification helpers |
fetch | No | Custom fetch implementation |
headers | No | Default headers added to every request |
Session-Scoped Usage
Use a session-scoped API key for messages, contacts, groups, session runtime operations, and webhooks.
Send a Message
const result = await sendable.messages.send({
chatId: '[email protected]',
text: { content: 'Hello from Sendable' },
})
console.log(result.messageId, result.status)Mention Contacts In a Group
await sendable.messages.send({
chatId: '[email protected]',
text: { content: 'asd @6285176702468' },
mentions: ['[email protected]'],
})Format WhatsApp Text
Use the SDK formatter helpers to build text.content strings without changing the send API:
import { Sendable, waText } from '@sendable-dev/sdk'
const content = waText()
.bold('Release update')
.newline()
.quote('Deployment completed')
.newline()
.bulletedList(['API healthy', 'Workers healthy'])
.build()
await sendable.messages.send({
chatId: '[email protected]',
text: { content },
})Send a Message Asynchronously
const queued = await sendable.messages.sendAsync(
{
chatId: '[email protected]',
text: { content: 'Queued message' },
},
{
idempotencyKey: 'order-123-notify',
},
)
const delivery = await sendable.messages.getDeliveryStatus(queued.runId)List Messages
const messages = await sendable.messages.list({
chatId: '[email protected]',
limit: 25,
})Decrypt Media Locally
Use the standalone SDK utility when you already have a WhatsApp media url and mediaKey. This runs in Node/Bun and does not proxy decrypted bytes through the Sendable API.
import { Sendable, decryptWhatsAppMedia } from '@sendable-dev/sdk'
const message = await sendable.messages.get('wamid.HBgLN...')
if (!message.image?.url || !message.image.key) {
throw new Error('Message does not include decryptable image metadata')
}
const media = await decryptWhatsAppMedia({
url: message.image.url,
mediaKey: message.image.key,
mediaType: 'image',
mimeType: message.image.mimetype,
messageId: message.id,
})
await Bun.write(media.fileName, media.data)Decrypt Webhook Media
If you store Sendable webhook events, you can pass the full event or event.data directly into the SDK wrapper without manually mapping url and mediaKey.
import { decryptWebhookMedia } from '@sendable-dev/sdk'
const media = await decryptWebhookMedia(event)
await Bun.write(media.fileName, media.data)Session Runtime Actions
const status = await sendable.sessions.status()
await sendable.sessions.start()
await sendable.sessions.restart()
await sendable.sessions.end()Contacts, Groups, and Webhooks
const contacts = await sendable.contacts.list({ limit: 20 })
const groups = await sendable.groups.list({ limit: 20 })
const webhooks = await sendable.webhooks.list({ page: 1, limit: 25 })Account-Scoped Usage
Use an account-scoped API key for account-level session management.
import { Sendable } from '@sendable-dev/sdk'
const accountClient = new Sendable({
apiKey: process.env.SENDABLE_ACCOUNT_API_KEY!,
apiKeyScope: 'account',
baseUrl: process.env.SENDABLE_API_URL,
})
const sessions = await accountClient.sessions.list()
const created = await accountClient.sessions.create({
state: 'ID',
name: 'Primary session',
})You can also fetch, update, and delete sessions:
const session = await accountClient.sessions.get(created.id)
await accountClient.sessions.update(created.id, {
name: 'Production session',
isActive: true,
})
await accountClient.sessions.delete(created.id)Webhook Verification
If you pass webhookSecret into the client, you can verify and parse Svix-signed webhooks with the SDK:
import express from 'express'
import { sendable } from './sendable'
const app = express()
app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
const payload = req.body.toString()
try {
const event = await sendable.webhooks.verifyAndParse(payload, req.headers)
console.log(event.type, event.data)
return res.json({ received: true })
} catch {
return res.status(401).json({ error: 'Invalid signature' })
}
})Error Handling
The SDK throws typed errors you can branch on:
import {
SendableApiError,
SendableScopeError,
} from '@sendable-dev/sdk'
try {
await sendable.messages.send({
chatId: '[email protected]',
text: { content: 'Hello' },
})
} catch (error) {
if (error instanceof SendableScopeError) {
console.error('Wrong API key scope for this operation')
} else if (error instanceof SendableApiError) {
console.error(error.status, error.message, error.body)
} else {
throw error
}
}Next Steps
- See Node.js Quick Start for the minimal setup flow
- See Express Integration for a full server example
- See API Reference for endpoint-level details