Skip to content
Start free

Contacts & opt-in

A contact is an end user reachable on one of your channels, identified by a stable id (aliased as contact_id) that is unique per phone number within your organization. Before you can send proactively to a WhatsApp number — a to send outside an existing conversation — that contact needs an active opt-in record. Replies inside the 24-hour window do not.

Record the contact and its opt-in in one call. phone is E.164. optIn.source is required when you pass an optIn block — capture where and how consent was given so it holds up to a WhatsApp quality review.

const contact = await wabery.contacts.enroll({
phone: "+14155550100",
name: "Ada Lovelace",
preferredLanguage: "en", // BCP-47; resolves locale-specific flows
referenceId: "user_123", // your own id; echoed back as contact_reference
metadata: { plan: "pro" },
optIn: {
source: "checkout_form", // required within optIn
method: "web_form",
occurredAt: "2026-06-20T14:00:00Z",
consentText: "I agree to receive order updates on WhatsApp.",
},
});

Requires the contacts:write scope. project_id is optional — API keys are project-scoped, so it defaults to the key’s project and must match it if set.

A contact response:

{
"object": "contact",
"id": "contact_...",
"contact_id": "contact_...",
"phone": "+14155550100",
"preferred_language": "en",
"reference_id": "user_123",
"project_id": "project_...",
"status": "active"
}

Use id in new code; contact_id is a backward-compatible alias with the same value. reference_id is your correlation key — it surfaces as contact_reference on every webhook for this contact.

metadata is shallow-merged into the existing value; preferredLanguage: null clears it. Requires contacts:write.

await wabery.contacts.update("contact_...", {
preferredLanguage: "es",
metadata: { linked: true }, // merged, not replaced
});

Cursor-paginated with limit and startingAfter (see Pagination). Requires contacts:read.

const { data, has_more } = await wabery.contacts.list({ limit: 50 });
// Or stream every page without managing the cursor:
for await (const contact of wabery.contacts.listAll()) {
console.log(contact.id, contact.phone);
}

unenroll deletes the contact record. Pass erase: true to also erase retained personal data (requires the contacts:erase scope) — use it for GDPR / right-to-be-forgotten requests.

await wabery.contacts.unenroll("contact_...", { erase: true });

Enrollment is a server-side action with a secret key — you already hold the number and consent. To let a customer opt in from your app without a secret key, mint a browser-safe registration intent instead: they tap a wa.me link, message your number, and Wabery links the WhatsApp identity to your customer_reference.

Sending messages

Send text, media, templates, and interactive messages to a contact.

Registration intents

Browser-safe opt-in links that link a WhatsApp identity to your user id.

Conversations & history

List a contact’s threads and read message history.