Upsert Contacts
Request
Section titled “Request”Send a JSON array of contacts (not an object). Each contact needs at least a
phone.
curl -X POST https://api.whatbot.in/public/v1/contacts \ -H "X-API-Key: wa_live_xxx" \ -H "Content-Type: application/json" \ -d '[ { "phone": "9876543210", "name": "Ravi Kumar", "opt_in": true, "tags": ["gold", "vip"], "source_ref": "crm-lead-4821", "attributes": { "email": "ravi@kumarjewels.com", "city": "Chennai", "budget": "15L", "product_interest": "gold coins" } } ]'const res = await fetch("https://api.whatbot.in/public/v1/contacts", { method: "POST", headers: { "X-API-Key": process.env.WHATBOT_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify([ { phone: "9876543210", name: "Ravi Kumar", opt_in: true, tags: ["gold", "vip"], source_ref: "crm-lead-4821", attributes: { email: "ravi@kumarjewels.com", city: "Chennai", budget: "15L" }, }, ]),});const data = await res.json(); // { upserted: 1, received: 1 }import requests
resp = requests.post( "https://api.whatbot.in/public/v1/contacts", headers={"X-API-Key": "wa_live_xxx"}, json=[ { "phone": "9876543210", "name": "Ravi Kumar", "opt_in": True, "tags": ["gold", "vip"], "source_ref": "crm-lead-4821", "attributes": {"email": "ravi@kumarjewels.com", "city": "Chennai", "budget": "15L"}, } ], timeout=20,)print(resp.json()) # {'upserted': 1, 'received': 1}Body fields
Section titled “Body fields”| Field | Type | Required | Description |
|---|---|---|---|
| phone | string | Yes | The contact’s phone number, any format. Auto-normalized to E.164 (98765 43210 → +919876543210). This is the unique key — re-sending the same number updates the existing contact. |
| name | string | Display name shown in the inbox. Won’t overwrite the real WhatsApp profile name on an active conversation — it fills in only where a name is missing. | |
| opt_in | boolean | Whether the contact has opted in to marketing messages. Used for broadcast eligibility. | |
| tags | string[] | Labels for segmenting broadcasts and filtering the inbox, e.g. ["gold","vip"]. | |
| source_ref | string | Your own reference id (e.g. the CRM record id). Stored for traceability; not shown to customers. | |
| attributes | object | Free-form custom fields — put anything you want here (email, city, budget, status, source, assigned rep, language…). No schema, no pre-declaration. These become {{variables}} usable in WhatsApp templates and show on the contact in the inbox. |
Response
Section titled “Response”| Field | Type | Required | Description |
|---|---|---|---|
| upserted | number | How many contacts were created or updated. | |
| received | number | How many contacts were in the request body. |
{ "upserted": 1, "received": 1 }How upsert works
Section titled “How upsert works”- Keyed by phone. Sending a phone that already exists updates that contact (merging the new fields) instead of creating a duplicate. Safe to re-run.
- Batch freely. Send one contact or thousands in a single array.
- Phone normalization applies a default country code to bare 10-digit numbers,
so
9876543210,+91 98765 43210, and919876543210all resolve to the same contact.
Custom attributes → template variables
Section titled “Custom attributes → template variables”Anything under attributes can be referenced in an approved WhatsApp template. If
you push attributes: { "city": "Chennai" }, a template body like
Hi, our {{city}} store has new stock renders per-contact automatically.
Errors
Section titled “Errors”| Status | Cause |
|---|---|
401 | Missing/invalid X-API-Key |
422 | Body wasn’t a JSON array, or a contact was missing phone / had an invalid number |