# Keep contacts in sync

> Copy customers from your own system into Soosh, and bring back the changes made in Soosh, without making duplicates.

Source: https://docs.soosh.io/guides/api/sync-contacts/

Your shop or CRM knows things about customers that Soosh doesn't, and Soosh
learns things in chats that your system doesn't. This guide keeps the two
lists matched both ways: you push your changes to Soosh, and you pull Soosh's
changes back.

## Before you start

- [An API key](https://docs.soosh.io/guides/integrations/create-an-api-key/) that can **List and read contacts** (`contacts:read`) and **Create, change and delete contacts and tags** (`contacts:write`)
- Optional: [a webhook endpoint](https://docs.soosh.io/guides/api/verify-webhooks/) for [`contact.created`](https://docs.soosh.io/reference/api/webhook-events/contact-created/), [`contact.updated`](https://docs.soosh.io/reference/api/webhook-events/contact-updated/) and [`contact.deleted`](https://docs.soosh.io/reference/api/webhook-events/contact-deleted/)

Match customers by phone number. Soosh keeps one contact per WhatsApp
number, and finds it whichever way you write the number: with or without
`+`, spaces or dashes, and with `00` for `+`. Mexican and Argentine mobiles
match however WhatsApp writes them (with or without the `1` after 52, or the
`9` after 54).

## 1. Push a new customer

[Create the contact](https://docs.soosh.io/reference/api/contacts/create-contact/) with an
[`Idempotency-Key`](https://docs.soosh.io/reference/api/#retrying-safely), so a retry never makes two:

```sh
curl https://app.soosh.io/v1/contacts \
  -H "Authorization: Bearer $SOOSH_API_KEY" \
  -H "Idempotency-Key: customer-8812" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+91 98765 43210",
    "name": "Priya Sharma",
    "attributes": { "customer_id": "8812", "city": "Pune", "orders": 3 },
    "tags": ["VIP"]
  }'
```

You get `201` and the [Contact](https://docs.soosh.io/reference/api/contacts/#object). Save its
`id` next to your customer.

If Soosh already has a contact with that number, for example because Priya
wrote to you first, you get `409` [`conflict`](https://docs.soosh.io/reference/api/errors/#conflict)
and `error.details.id` is that contact. Save that `id`, then change the contact as in step 2. A contact
deleted earlier is brought back instead, with its history, and that's a
`201` too.

To find a contact by number at any time:

```sh
curl "https://app.soosh.io/v1/contacts?phone=%2B919876543210" \
  -H "Authorization: Bearer $SOOSH_API_KEY"
```

## 2. Push changes

[Update a contact](https://docs.soosh.io/reference/api/contacts/update-contact/) with only the fields that changed:

```sh
curl -X PATCH https://app.soosh.io/v1/contacts/$CONTACT_ID \
  -H "Authorization: Bearer $SOOSH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "attributes": { "city": "Mumbai", "orders": 4, "old_field": null } }'
```

- **Attributes are merged.** Keys you leave out stay. A key set to `null` is
  removed. A contact has at most 50. A name is 1 to 64 letters, digits or
  underscores and can't start with `__`. A text value is at most 1,024
  characters.
- **The name is yours once you set it.** A name you set over the API (or in
  the app) isn't replaced by the customer's WhatsApp profile name. That one
  stays in `whatsapp_name`. Send `"name": ""` to go back to the WhatsApp
  name.
- **Tags have their own requests.** `PUT /v1/contacts/{id}/tags/{tag}` adds
  one and `DELETE` removes it. Both are safe to repeat. A contact has at
  most 50 tags.
- **Opt-outs:** set `marketing_opt_out` to `true` when a customer tells you
  they don't want marketing messages. Soosh then leaves them out of marketing
  broadcasts and refuses marketing templates to them.

When you delete a customer in your system, delete the contact:
`DELETE /v1/contacts/{id}`.

## 3. Pull changes made in Soosh

Customers write in, agents edit names and tags, and imports add contacts.
Pick up those changes in one of two ways, or both.

### With webhooks, as they happen

Subscribe an endpoint to these events. Each event comes in the
[envelope](https://docs.soosh.io/reference/api/webhook-events/#the-envelope). See
[Verify webhooks](https://docs.soosh.io/guides/api/verify-webhooks/) to check each one.

| Event | Sent when | What to do |
|---|---|---|
| `contact.created` | A contact is added, however it came in | Add the customer, or link it to yours by phone. |
| `contact.updated` | The name, attributes, tags, opt-out or number changed | `data.changed` lists the fields. Copy them from `data.contact`. |
| `contact.deleted` | A contact is deleted | Unlink or delete your customer. `data` has `contact_id` and `phone`. |

Events can arrive out of order. Compare `updated_at` with what you have, and
skip an older one.

### By asking, to catch up

[List contacts](https://docs.soosh.io/reference/api/contacts/list-contacts/) by when they changed, oldest change first
(see [Pagination](https://docs.soosh.io/reference/api/#pagination)):

```sh
curl "https://app.soosh.io/v1/contacts?sort=updated_at&limit=100" \
  -H "Authorization: Bearer $SOOSH_API_KEY"
```

1. Save each contact.
2. If `next_cursor` isn't `null`, ask again with `cursor` set to it.
3. When `next_cursor` is `null`, you're up to date. Save the `updated_at`
   of the last contact you got.
4. Next time, start with `updated_after` set to a minute before that time,
   and skip contacts whose `updated_at` you already have. A contact that
   changed since has moved to the end of the list, so you get it again.

The list shows a change about 30 seconds after it's saved. A change that is
still being saved can carry an earlier time than one already listed; the
wait lets almost all of them land first, and going back a minute catches
the rest. A contact you just changed shows up about half a minute later.

To start over from a time instead, send `updated_after`, like
`updated_after=2026-10-15T00:00:00Z`.

> **Note:** A deleted contact leaves the list, so asking can't tell you about deletions.
> Use the `contact.deleted` webhook for those.

## Result

New customers and changes flow both ways. Your system and Soosh agree on
each customer, matched by phone number, with no duplicates.

## Related

- [Contacts API](https://docs.soosh.io/reference/api/contacts/)
- [Webhook events](https://docs.soosh.io/reference/api/webhook-events/)
- [Errors](https://docs.soosh.io/reference/api/errors/)
- [Handle errors and limits](https://docs.soosh.io/guides/api/errors-and-limits/)
- [Marketing opt-out](https://docs.soosh.io/concepts/marketing-opt-out/)
