SDK Quickstart
This guide takes you from bun install to a delivered WhatsApp message
using @kirimdev/sdk. By the end you’ll know how to scope the client
to a phone number, send the three common message types, and check
delivery status.
Prerequisites: a kirim.dev API key (kdv_live_...) and at least one
connected WhatsApp Business number. If you don’t have either, work
through the Platform Quickstart first.
-
Install the SDK.
Terminal window bun add @kirimdev/sdkTerminal window npm install @kirimdev/sdkTerminal window pnpm add @kirimdev/sdkTerminal window yarn add @kirimdev/sdkimport { Kirim } from 'npm:@kirimdev/sdk'Types ship with the package — no
@types/...install needed. Make sure yourpackage.jsonhas"type": "module"; the SDK is ESM only. -
Create the client.
src/kirim.ts import { Kirim } from '@kirimdev/sdk'export const kirim = new Kirim({apiKey: process.env.KIRIM_KEY!,}) -
Find your
phone_number_id.Every send is scoped to one WhatsApp number. List your accounts and grab the
idyou want to send from:import { kirim } from './kirim'const accounts = await kirim.accounts.list()for (const a of accounts.data) {console.log(a.id, a.display_phone_number, a.verified_name)}// -> 106540352242922 +62 811 2222 333 Acme IndonesiaCache that id at module top-level so you don’t fetch it on every request:
src/kirim.ts export const phone = kirim.phoneNumbers('106540352242922') -
Send a text message.
src/send-text.ts import { phone } from './kirim'const msg = await phone.messages.send({messaging_product: 'whatsapp',to: '+628111222333',type: 'text',text: { body: 'Halo dari @kirimdev/sdk!' },})console.log(msg.id, msg.status) // -> wamid.xxx queuedTerminal window bun src/send-text.tsTerminal window node --import tsx src/send-text.tsIdempotency is automatic: the SDK injects a UUID
Idempotency-Keyon every POST and reuses it across retries — re-running this script after a transient failure will never send twice. -
Send a template message.
Templates are the only way to start a conversation outside the 24-hour window. Look up an approved template first, then send:
src/send-template.ts import { phone } from './kirim'// Confirm the template is approvedconst tpl = await phone.templates.retrieve('order_confirmation', {language: 'id_ID',})if (tpl.status !== 'approved') {throw new Error(`Template not approved: ${tpl.status}`)}const msg = await phone.messages.send({messaging_product: 'whatsapp',to: '+628111222333',type: 'template',template: {name: 'order_confirmation',language: { code: 'id_ID' },components: [{type: 'body',parameters: [{ type: 'text', text: 'Budi' },{ type: 'text', text: 'ORD-1042' },],},],},})console.log(msg.id) -
Send a media message (image).
Pass a public URL — kirim.dev re-uploads to Meta and tracks the media id for you.
src/send-image.ts import { phone } from './kirim'const msg = await phone.messages.send({messaging_product: 'whatsapp',to: '+628111222333',type: 'image',image: {link: 'https://cdn.example.com/invoice/ORD-1042.png',caption: 'Invoice for order ORD-1042',},})console.log(msg.id)Document, audio, and video work the same way — swap
typeand the matching payload key. See the per-type pages under Sending & receiving for every variant. -
Verify delivery.
messages.sendreturns immediately withstatus: 'queued'. The real status is delivered by webhook callbacks from Meta. For a quick check, poll:import { phone } from './kirim'const fresh = await phone.messages.retrieve(msg.id)console.log(fresh.status) // -> sent / delivered / read / failedFor production, subscribe to webhooks instead of polling — see SDK Webhooks for the verifier and typed event handlers.
Configuration knobs
Section titled “Configuration knobs”Tune the client for your environment:
const kirim = new Kirim({ apiKey: process.env.KIRIM_KEY!,
// Override for staging / on-prem deployments baseUrl: 'https://api-staging.kirim.chat/v1',
// Tighten the request budget — useful in edge functions timeout: 10_000,
// 0 disables auto-retry entirely (you'll see 429 / 5xx directly) maxRetries: 4,
// Identify your app in our logs (helps with support tickets) userAgentSuffix: 'acme-billing/2.4.0',
// Inject a custom transport (e.g. undici, Node fetch with HTTPS agent) fetch: customFetch,})| Option | Default | When to change |
|---|---|---|
apiKey | — | Required. Use kdv_live_* in production, kdv_test_* in tests. |
baseUrl | https://api.kirim.chat/v1 | Pointing to staging, on-prem, or a local mock. |
timeout | 30_000 ms | Cap latency in edge runtimes; raise for media-heavy uploads. |
maxRetries | 2 | 0 for strict one-shot calls; higher for unreliable upstreams. |
userAgentSuffix | none | Always set it — makes support triage faster. |
fetch | globalThis.fetch | Custom transport, mocking, or to add proxy / cert pinning. |
What’s next
Section titled “What’s next”- Errors — the
KirimErrorhierarchy and recipes for handling each subclass. - Pagination —
for awaitover every page, or step manually with.page(). - Webhooks — verify deliveries from Hono, Express, Next.js, or Bun.serve.
- Sending & receiving — every message type with payload examples (text, interactive, media, template).
- Concepts → Idempotency — what the SDK does for you under the hood.