Recipe: sync contacts with your CRM
Last updated: 11 November 2026 · 9 min read
This recipe keeps Clomni contacts in step with your CRM: Bitrix24, amoCRM, HubSpot or your own system. You load all contacts once, then fetch only what changed, and write from the CRM back to Clomni when needed.
Steps
-
1
What you get
- Every customer's Clomni contact in the CRM: name, phone, email, custom attributes.
- A contact created or changed in Clomni reaches the CRM within seconds.
- Data changed in the CRM (for example the customer tier) is written to Clomni and the agent sees it in the conversation.
- If you want, the customer's conversation history in the CRM.
-
2
Scopes you need
contacts:read(medium risk): read contacts and thecontact.*webhooks.contacts:write(medium risk): write from the CRM to Clomni. If data only flows from Clomni to the CRM, do not ask for it.custom_attributes:read(low risk): the list of custom attributes.conversations:readandmessages:read(medium risk): only if the CRM shows the conversation history.
When creating the app you can pick the "CRM sync" preset and then remove what you do not need.
contacts:writealso grantscontacts:read, but to pick a webhook topiccontacts:readitself has to be approved. App and token: Create an app on developer.clomni.ai: step by step, Tokens and authentication in the Developer API. -
3
1. Load all contacts once
GET /v1/sync/contactsreturns contacts ordered by(updated_at, id), oldest change first.limitis 1 to 100. For the next page send thenext_cursorfrom the response asafter. Whenhas_moreisfalse, you have everything.curl "https://api.clomni.ai/v1/sync/contacts?limit=100" \ -H "Authorization: Bearer clomni_pat_xxx"
{ "data": [ { "id": 981, "name": "Leyla Hasanova", "email": "[email protected]", "phone_number": "+994501234567", "identifier": "crm-10422", "custom_attributes": { "customer_tier": "gold" }, "created_at": "2026-07-25T11:02:00.000Z", "updated_at": "2026-11-08T09:14:03.120Z" } ], "next_cursor": "MjAyNi0xMS0wOFQwOToxNDowM1p8OTgx", "has_more": true }Do not try to decode or build
next_cursoryourself; send it back as it is. -
4
2. Then fetch only what changed
After the first load, store the largest
updated_at. Next time, get what changed after it withupdated_since:curl "https://api.clomni.ai/v1/sync/contacts?updated_since=2026-11-08T09:12:00Z&limit=100" \ -H "Authorization: Bearer clomni_pat_xxx"
- Go back 1-2 minutes from the stored time so records written at the same moment are not lost.
- Write contacts by
id(upsert): if a contact comes twice, the newer copy replaces the older one. - While
has_moreistrue, continue withafterand keepupdated_sinceunchanged. - Store the new time only after every page has been written.
The sync endpoints allow 60 requests a minute per token. Running this every few minutes is enough.
-
5
3. Get changes right away
On the developer portal, in the app's "Webhooks" section, enter your HTTPS URL and pick the
contact.createdandcontact.updatedtopics. The event carries the full contact. Webhooks can arrive twice and in any order: checkupdated_atand drop an older event if the CRM record is newer. Keep step 2 running for the times webhooks fail, for example once an hour. Setup and signature checks: App webhooks and events: setup, topics, signature verification. -
6
4. Write from the CRM to Clomni
Put the CRM ID in the contact's
identifier: it is unique within the workspace. First search whether the contact is already in Clomni (the search covers name, email, phone andidentifier):curl "https://api.clomni.ai/v1/contacts/search?q=crm-10422" \ -H "Authorization: Bearer clomni_pat_xxx"
If not, create it:
curl -X POST "https://api.clomni.ai/v1/contacts" \ -H "Authorization: Bearer clomni_pat_xxx" \ -H "Content-Type: application/json" \ -d '{"name": "Leyla Hasanova", "email": "[email protected]", "phone_number": "+994501234567", "identifier": "crm-10422", "custom_attributes": {"customer_tier": "gold"}}'If it is, update it.
PATCHchanges only the fields you send, andcustom_attributesare merged with the existing ones:curl -X PATCH "https://api.clomni.ai/v1/contacts/981" \ -H "Authorization: Bearer clomni_pat_xxx" \ -H "Content-Type: application/json" \ -d '{"custom_attributes": {"customer_tier": "platinum"}}'Phone numbers must be in international format:
+and the country code, no spaces. Creating a second contact with the same email or phone returns 422. -
7
5. Map custom attributes
custom_attributeskeys must exist in the workspace beforehand. The list:curl "https://api.clomni.ai/v1/custom_attribute_definitions?attribute_model=contact_attribute" \ -H "Authorization: Bearer clomni_pat_xxx"
Map the CRM fields to these keys once. A key that is not defined does not show in the dashboard. Create new keys in the dashboard or through the API (
custom_attributes:write). -
8
6. Avoid an endless loop
In a two-way sync, your own change comes back to you as a webhook. If you write it to the CRM again, you get a loop. When you write to Clomni, store the
updated_atfrom the response and drop an event that comes with the sameupdated_at. Or store a hash of the synced fields and do nothing if it has not changed. -
9
Common mistakes
updated_sinceis stored without overlap and contacts changed in the same second are lost. Go back 1-2 minutes.updated_sinceis changed between pages. Page only withafter.- A contact is created without checking whether it exists, and 422 comes back. Search first, then update.
- The phone is written as
0501234567, without the country code. - A key that does not exist in the workspace is written into
custom_attributesand does not show in the dashboard. contacts:writeis approved butcontacts:readis not, so thecontact.*webhook cannot be picked.- After a 429 the request is retried at once. Wait for the number of seconds in
Retry-After. - Time formats get mixed up: the sync endpoints return ISO 8601 (UTC), the older lists return Unix seconds.
All contact endpoints: Scopes and endpoints: contacts, labels, custom attributes, canned responses.