Audience
Contacts
Keep the people you message as contacts, group them with tags, lists and segments, and record who agreed to hear from you.
What a contact is#
A contact is a person your account can message: a name, a phone number in E.164, optionally an email address and recipient IDs for channels that do not use a phone number, plus your own attributes and tags. Contacts belong to the account and are the same for live and test keys.
You do not need contacts to send single messages with POST /v1/messages. You need them for campaigns, and they are where unsubscribes are recorded.
{
"id": "ct_8Jk3mP6qR9sT2vW5xY1z",
"object": "contact",
"first_name": "Layla",
"last_name": "Haddad",
"phone": "+971501234567",
"email": "layla@example.com",
"channel_identifiers": {
"telegram": "482910375"
},
"locale": "ar",
"timezone": "Asia/Dubai",
"attributes": {
"city": "Dubai",
"orders": 12
},
"tags": [
"vip",
"newsletter"
],
"consent": {
"whatsapp": {
"state": "opted_in",
"source": "api",
"updated_at": "2026-10-01T08:00:00.000Z"
}
},
"blocked": false,
"blocked_reason": null,
"last_messaged_at": "2026-10-05T09:30:00.000Z",
"created_at": "2026-10-01T08:00:00.000Z",
"updated_at": "2026-10-05T09:30:00.000Z"
}Create and update#
POST /v1/contacts/upsert is the call most integrations want: it creates the contact, or updates the one that already has the phone number. A number without a country code is read as a number of your account country; anything that is not a phone number is rejected with 400 parameter_invalid.
curl https://api.omnimessage.co/v1/contacts/upsert \
-H "Authorization: Bearer om_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Layla",
"last_name": "Haddad",
"phone": "+971501234567",
"email": "layla@example.com",
"locale": "ar",
"timezone": "Asia/Dubai",
"tags": [
"vip",
"newsletter"
],
"consent": {
"whatsapp": "opted_in"
}
}'POST /v1/contactscreates and fails with409 contact_existswhen the number is taken.PATCH /v1/contacts/{id}changes the fields it carries.tagsreplaces the whole tag set; usePOST /v1/contacts/tagsto add or remove tags on up to 1,000 contacts at once.DELETE /v1/contacts/{id}removes the contact from every list. Messages already sent stay in the message log.- Keys need the scopes
contacts:readandcontacts:write.
Attributes#
Attributes are your own fields, such as a city or an order count. Define each one in the console under Contacts → Attributes with a key and a type (text, number, yes / no, date); the API then accepts a value for that key and rejects unknown keys. Attributes can be used in segments and as merge tags in campaign messages: {{attributes.city}}.
Tags, lists and segments#
| What it is | How it changes | |
|---|---|---|
| Tag | A label on the contact, such as vip. | You set it: tags on the contact or POST /v1/contacts/tags. |
| List | A fixed group you choose, such as newsletter subscribers. | POST /v1/contact_lists/{id}/members and /members/remove. |
| Segment | A saved rule, such as "VIP in Dubai who opted in". | On its own: members are worked out every time the segment is used. |
Segments are built in the console with a rule builder that shows the matching count as you type. The API reads them: GET /v1/segments, and GET /v1/segments/{id}/preview for the current count and the first matches.
Consent and unsubscribes#
Each contact has a consent state per channel type: opted_in, opted_out, or none (unknown). Set it with the consent field when you know it, for example { "consent": { "whatsapp": "opted_in" } } after a checkout opt-in.
- When a contact replies with a stop word (STOP, UNSUBSCRIBE and equivalents in other languages) on a channel, they become
opted_outfor that channel type. START opts them back in. - A contact that is
opted_outon a channel type, or blocked, is left out of every campaign on it. This also holds for numbers you paste into a campaign. - Every change is kept with its source and time; the console shows the history on the contact.
Importing files#
The console imports CSV and Excel files: Contacts → Import. You upload the file, match its columns to contact fields or attributes, choose what happens to phone numbers that already exist, and confirm that the people in the file agreed to be contacted. The import runs in the background; rows that could not be imported are listed in a downloadable report with the reason for each. From code, loop over POST /v1/contacts/upsert instead.