TypeScript SDK
@kirimdev/sdk wraps every endpoint with auto-retry, idempotency,
async pagination, and typed errors.
Read →
You need a connected Kirim organization with at least one approved WhatsApp Business number. If you haven’t onboarded yet, sign up at app.kirim.dev and connect your number first — the dashboard walks you through Meta’s embedded signup flow.
By the end of this page you’ll have:
phone_number_id (Meta’s stable account identifier).text message to your own phone.delivered.Total time: under five minutes if Meta is in a cooperative mood.
Issue an API key.
In the Kirim dashboard, navigate to Developers → API Keys and
click Create key. Give it a descriptive label (e.g.
local-laptop or n8n-prod) and copy the plaintext value
immediately — Kirim shows it exactly once.
# Store the key in your shell — never commit it.export KIRIM_KEY=kdv_live_xxxxxxxxxxxxxxxxxxxxxxxxVerify the key works.
The /v1/me endpoint introspects your key and returns the
organization context.
curl -sS https://api.kirim.chat/v1/me \ -H "Authorization: Bearer $KIRIM_KEY"Expected response:
{ "data": { "organization": { "id": "org_…", "object": "organization", "name": "Your Org" }, "api_key": { "id": "key_…", "object": "api_key", "label": "local-laptop", "last4": "abcd" }, "rate_limits": { "tier": "default", "write_per_minute": 60, "read_per_minute": 600 } }, "request_id": "req_…"}Resolve your phone number ID.
Kirim uses Meta-style path routing — sender lives in the URL, not
the body. Look up your connected accounts and grab the
phone_number_id:
curl -sS https://api.kirim.chat/v1/accounts \ -H "Authorization: Bearer $KIRIM_KEY"{ "data": [ { "object": "account", "phone_number_id": "106540352242922", "phone_number": "+628111222333", "name": "Customer Support", "status": "connected" } ], "has_more": false, "next_cursor": null, "request_id": "req_…"}Stash the id — every per-account endpoint takes it as a path segment.
export PHONE_ID=106540352242922Send your first message.
Replace +628123456789 with a phone number Meta has already
received a message from in the past 24 hours — Meta only allows
free-form text inside an open conversation window.
// bun add @kirimdev/sdkimport { Kirim } from '@kirimdev/sdk'
const kirim = new Kirim({ apiKey: process.env.KIRIM_KEY! })const phone = kirim.phoneNumbers(process.env.PHONE_ID!)
const msg = await phone.messages.send({ messaging_product: 'whatsapp', to: '+628123456789', type: 'text', text: { body: 'Halo dari Kirim' },})console.log(msg.id) // -> msg_...The official SDK adds auto-retry, automatic Idempotency-Key,
async pagination, and typed errors. Cache the per-phone client
at module top-level for the common single-account case. See
the SDK Quickstart for the
full walkthrough.
curl -sS "https://api.kirim.chat/v1/$PHONE_ID/messages" \ -H "Authorization: Bearer $KIRIM_KEY" \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "to": "+628123456789", "type": "text", "text": { "body": "Halo dari Kirim" } }'const res = await fetch( `https://api.kirim.chat/v1/${process.env.PHONE_ID}/messages`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.KIRIM_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ messaging_product: 'whatsapp', to: '+628123456789', type: 'text', text: { body: 'Halo dari Kirim' }, }), },)const json = await res.json()console.log(json.data.id) // -> msg_...import osimport httpx
r = httpx.post( f"https://api.kirim.chat/v1/{os.environ['PHONE_ID']}/messages", headers={"Authorization": f"Bearer {os.environ['KIRIM_KEY']}"}, json={ "messaging_product": "whatsapp", "to": "+628123456789", "type": "text", "text": {"body": "Halo dari Kirim"}, },)print(r.json()["data"]["id"]) # -> msg_...package main
import ( "bytes" "encoding/json" "fmt" "net/http" "os")
func main() { body, _ := json.Marshal(map[string]any{ "messaging_product": "whatsapp", "to": "+628123456789", "type": "text", "text": map[string]string{"body": "Halo dari Kirim"}, }) url := fmt.Sprintf( "https://api.kirim.chat/v1/%s/messages", os.Getenv("PHONE_ID"), ) req, _ := http.NewRequest("POST", url, bytes.NewReader(body)) req.Header.Set("Authorization", "Bearer "+os.Getenv("KIRIM_KEY")) req.Header.Set("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() // read & decode `data.id`...}Expected response:
{ "data": { "id": "msg_01HXYZABCDEFGHJKMNPQRSTVWX", "object": "message", "to": "+628123456789", "type": "text", "status": "pending", "created_at": "2026-05-26T08:00:00.000Z" }, "request_id": "req_…"}The recipient should receive the message within a few seconds.
Poll the status.
The send is asynchronous — status flips from pending →
sent → delivered → read as Meta returns callbacks.
curl -sS \ "https://api.kirim.chat/v1/$PHONE_ID/messages/msg_01HXYZABCDEFGHJKMNPQRSTVWX" \ -H "Authorization: Bearer $KIRIM_KEY"{ "data": { "id": "msg_01HXYZABCDEFGHJKMNPQRSTVWX", "object": "message", "status": "delivered", "to": "+628111111111", "type": "text", "created_at": "…" }, "request_id": "…"}Polling is fine for a quickstart but wasteful at scale. Set up a
webhook (next section) to receive message.status callbacks
instead.
TypeScript SDK
@kirimdev/sdk wraps every endpoint with auto-retry, idempotency,
async pagination, and typed errors.
Read →
Subscribe to webhooks
Stop polling, start receiving events as they happen. Read →
Authentication deep dive
Key rotation, expiration, security guidance. Read →
Handle failed sends
Structured error envelope and stable error codes you can branch on. Read →
API Reference
Every endpoint, every parameter, every error code. Browse →
Recipes
End-to-end patterns for common workflows. Browse →