Sending messages
Send text, media, templates, and interactive messages to a contact.
A contact is an end user reachable on one of your channels, identified by a
stable Wabery id (aliased as contact_id). A phone number is optional: Meta
can represent a WhatsApp username user only by a Business Scoped User ID
(BSUID). Wabery namespaces that identity to the owning Meta business portfolio
and updates it when Meta sends user_id_update. 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.", },});wabery contacts enroll --project-id "project_id" \ --phone "+14155550100" \ --preferred-language "en" \ --metadata '{"plan":"pro"}'curl https://api.wabery.com/v1/contacts \ -H "Authorization: Bearer $WABERY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone": "+14155550100", "name": "Ada Lovelace", "preferred_language": "en", "reference_id": "user_123", "metadata": { "plan": "pro" }, "opt_in": { "source": "checkout_form", "method": "web_form", "occurred_at": "2026-06-20T14:00:00Z", "consent_text": "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.
Import up to 100,000 rows (25 MB) asynchronously. The only required mapped
column is phone; values must be E.164. Optional mappings cover name, preferred
language, tags, and metadata. A tags cell uses | or ; separators.
wabery contacts import contacts.csv \ --phone-column phone \ --name-column name \ --language-column language \ --tags-column tags \ --consent '{"categories":["MARKETING"],"source":"signup_form","method":"web_form"}'
wabery contacts importswabery contacts import-status "import_id"With the SDK:
const contactImport = await wabery.contacts.import({ file: new Blob([csvText], { type: "text/csv" }), filename: "contacts.csv", mapping: { phone: "phone", name: "name", preferredLanguage: "language", tags: "tags", }, defaults: { tags: ["august-launch"] }, consent: { categories: ["MARKETING"], source: "signup_form", method: "web_form", occurredAt: "2026-07-20T12:00:00Z", consentText: "Send me product announcements on WhatsApp.", },});
const status = await wabery.contacts.getImport(contactImport.id);Poll until status is completed. The response reports created, updated,
failed, and processed row counts. Rejected rows are available from
GET /contact-imports/{id}/errors.
Consent is category-specific. Add it after enrollment when it was captured in a separate product flow:
await wabery.contacts.addConsent("contact_...", { categories: ["MARKETING"], source: "account_preferences", method: "web_form", occurredAt: "2026-07-20T12:00:00Z", consentText: "Send me product announcements on WhatsApp.", evidence: { formVersion: "v3" },});wabery contacts consent "contact_..." \ --categories MARKETING \ --source account_preferences \ --method web_formSuppress a contact globally by omitting the category, or suppress only one category:
await wabery.contacts.suppress("contact_...", { category: "MARKETING", reason: "Requested by customer",});wabery contacts suppress "contact_..." \ --category MARKETING --reason "Requested by customer"Inbound STOP, UNSUBSCRIBE, CANCEL, END, or QUIT creates a suppression
automatically. Recording a later consent does not lift the suppression.
After independently verifying a genuine re-enrollment request, lift the
suppression explicitly; do not manufacture a consent record to bypass an
opt-out.
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});wabery contacts update "contact_..." --preferred-language "es" \ --metadata '{"linked": true}'curl -X PATCH https://api.wabery.com/v1/contacts/contact_... \ -H "Authorization: Bearer $WABERY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "preferred_language": "es", "metadata": { "linked": true } }'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 project contact record. Pass erase: true to also erase
retained personal data, including stored broadcast personalization, flow
submissions and dispatches, flow-event details, and connector invocation
payloads (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.
WhatsApp broadcasts
Send an approved template to a consent-eligible audience.