Skip to content
TypeScript SDK

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.

  1. Install the SDK.

    Terminal window
    bun add @kirimdev/sdk

    Types ship with the package — no @types/... install needed. Make sure your package.json has "type": "module"; the SDK is ESM only.

  2. Create the client.

    src/kirim.ts
    import { Kirim } from '@kirimdev/sdk'
    export const kirim = new Kirim({
    apiKey: process.env.KIRIM_KEY!,
    })
  3. Find your phone_number_id.

    Every send is scoped to one WhatsApp number. List your accounts and grab the id you 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 Indonesia

    Cache that id at module top-level so you don’t fetch it on every request:

    src/kirim.ts
    export const phone = kirim.phoneNumbers('106540352242922')
  4. 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 queued
    Terminal window
    bun src/send-text.ts

    Idempotency is automatic: the SDK injects a UUID Idempotency-Key on every POST and reuses it across retries — re-running this script after a transient failure will never send twice.

  5. 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 approved
    const 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)
  6. 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 type and the matching payload key. See the per-type pages under Sending & receiving for every variant.

  7. Verify delivery.

    messages.send returns immediately with status: '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 / failed

    For production, subscribe to webhooks instead of polling — see SDK Webhooks for the verifier and typed event handlers.

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,
})
OptionDefaultWhen to change
apiKeyRequired. Use kdv_live_* in production, kdv_test_* in tests.
baseUrlhttps://api.kirim.chat/v1Pointing to staging, on-prem, or a local mock.
timeout30_000 msCap latency in edge runtimes; raise for media-heavy uploads.
maxRetries20 for strict one-shot calls; higher for unreliable upstreams.
userAgentSuffixnoneAlways set it — makes support triage faster.
fetchglobalThis.fetchCustom transport, mocking, or to add proxy / cert pinning.
  • Errors — the KirimError hierarchy and recipes for handling each subclass.
  • Paginationfor await over 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.