Contacts
A contact is a person you can message across WhatsApp, SMS, and email. Every send targets an existing contact, so contacts are usually the first thing your integration creates. A contact is identified by its phone (E.164) and/or email.
Create or update (upsert)
bt.contacts.upsert creates a contact, or updates the one already matched by the same phone or email — so it is safe to call on every sync without creating duplicates:
const contact = await bt.contacts.upsert({
phone: "+27821234567",
email: "sam@example.com",
first_name: "Sam",
last_name: "Mokoena",
tags: ["vip"],
});
console.log(contact.id); // "con_123"The contact shape
Channel deliverability flags are nested under channels — they are derived server-side from the identifiers on the record (a contact with a phone gets has_whatsapp/has_sms; an email gets has_email):
{
"id": "con_123",
"business_id": null,
"first_name": "Sam",
"last_name": "Mokoena",
"phone": "+27821234567",
"email": "sam@example.com",
"tags": ["vip"],
"channels": {
"has_whatsapp": true,
"has_sms": true,
"has_email": true
},
"suppressed": false,
"created_at": "2026-05-22T10:30:00.000Z",
"updated_at": "2026-05-22T10:30:00.000Z"
}Read a single flag as contact.channels.has_whatsapp — not contact.has_whatsapp. The top-level suppressed flag is the do-not-contact gate; see Suppression and opt-outs.
List contacts
Listing is cursor-paginated. Pass limit and the next_cursor from the previous page:
let cursor: string | null | undefined;
do {
const page = await bt.contacts.list({ limit: 100, cursor: cursor ?? undefined });
for (const c of page.data) {
// ... process each contact
}
cursor = page.next_cursor;
} while (cursor);Get one contact
const contact = await bt.contacts.get("con_123");Update a contact
update is a partial PATCH: only the fields you pass change, and the body must contain at least one field. Passing null for phone or email clears it (and the matching channel flag); omitting a field leaves it untouched.
await bt.contacts.update("con_123", {
first_name: "Samuel",
tags: ["vip", "renewed"],
});
// Clear an email and its has_email flag:
await bt.contacts.update("con_123", { email: null });Toggling the do-not-contact gate is also a contact update — set suppressed: true to block every channel for the contact, or false to re-enable. Contact changes emit contact.created / contact.updated webhooks.