# External CRM Integration & Contact Syncing

## Overview

QuickReply.ai can sync your **Contacts/Leads** to any external CRM that exposes HTTP APIs or webhooks.

With this integration you can:

* **Create new leads** in your CRM whenever QuickReply.ai captures a new contact
* **Update existing leads** in your CRM whenever contact attributes change in QuickReply.ai
* Optionally **fetch and de-duplicate** leads in your CRM before creating new ones

QuickReply.ai calls **your CRM APIs** over HTTPS (JSON or form-encoded), using your auth mechanism (e.g. static API key).

***

## How it Works (High-level Flow)

{% @mermaid/diagram content="sequenceDiagram
participant QR as QuickReply.ai
participant CRM as External CRM

```
Note over QR: Contact changes in QuickReply.ai

alt New Lead Flow
    QR->>CRM: Create Lead
    CRM-->>QR: lead_id returned
    QR->>QR: Store lead_id
else Existing Lead Flow
    QR->>CRM: Update Lead (using stored lead_id)
    CRM-->>QR: Update confirmed
    QR->>QR: Lead data refreshed
end

Note over QR:  Sync completes (debounced ~5 mins)" %}
```

{% stepper %}
{% step %}
**A contact changes in QuickReply.ai**

* New contact created (from Popup / Website Chat/ WhatsApp chat or Instagram Chat, etc.)
* Existing contact updated (name, email, tags, attributes, etc.)
  {% endstep %}

{% step %}
**QuickReply.ai prepares a payload**

Typical fields:

* `phone` – with country code (recommended primary identifier)
* `email` – optional identifier
* `name` – full name of the contact
* `custom_fields` – key–value attributes mapped to your CRM schema
* Optionally: `lead_id` / `contact_id` – if previously returned by your CRM
  {% endstep %}

{% step %}
**QuickReply.ai calls your CRM endpoints**

Depending on configuration, QuickReply.ai will:

* Directly call **Create Lead** for new contacts
* Call **Fetch Lead** first (using phone/email/lead\_id), then decide to **Create** or **Update**
* Call **Update Lead** whenever an existing contact changes in QuickReply.ai
  {% endstep %}

{% step %}
**CRM returns its Lead/Contact ID**

* On success, your CRM returns a unique identifier (e.g. `lead_id` / `contact_id`).
* QuickReply.ai stores this and uses it for subsequent updates to avoid duplicates.
  {% endstep %}

{% step %}
**Sync Timing**

* Contacts are synced in **near real-time**.
* By default, QuickReply.ai waits for \~5 minutes of inactivity before syncing to avoid noisy updates and excessive API calls.
  {% endstep %}
  {% endstepper %}

***

## Pre-requisites

To use this integration, you need:

* An active **QuickReply.ai** account
* An active **CRM** with APIs to:
  * Create a lead/contact
  * Fetch a lead/contact
  * Update a lead/contact
* A static **Auth Key / token** or similar mechanism to secure your APIs

***

## API Contract (Your CRM)

QuickReply.ai expects your CRM to expose **three** HTTP endpoints:

1. [**Create Lead / Contact**](#id-1.-creating-a-new-lead-crm-api)
2. [**Fetch Lead / Contact**](#id-2.-fetching-an-existing-lead-crm-api)
3. [**Update Lead / Contact**](#id-3.-updating-an-existing-lead-crm-api)

You can use any URL structure you like (e.g. `/api/leads/create` instead of `/crm/create`) as long as it matches the contract below.

{% hint style="success" %}
🔐 All endpoints should:

* Be served over HTTPS
* Accept JSON payloads (preferred) or form-encoded body
* Validate a shared `Auth-Key` (or equivalent) in the headers
  {% endhint %}

### 1. Creating a new Lead - CRM API

#### Endpoint

```http
POST https://yourdomain.com/crm/create
```

#### Headers

| Name                                       | Type                 | Description                            |
| ------------------------------------------ | -------------------- | -------------------------------------- |
| Auth-Key<mark style="color:red;">\*</mark> | Authorization Header | Your shared API key / token (required) |

#### Request Body

| Name                                    | Type            | Description                                                                                                                      |
| --------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| phone<mark style="color:red;">\*</mark> | string          | Phone number with country code                                                                                                   |
| name<mark style="color:red;">\*</mark>  | string          | Full name of the contact                                                                                                         |
| email                                   | string          | Email address of the contact                                                                                                     |
| custom\_fields                          | Key-Value Pairs | Extra Attributes which you want to sync to the CRM, where the **key-names** are the respective **schema field names** in the CRM |

{% hint style="success" %}
Fields marked with <mark style="color:red;">\*</mark> are Required.
{% endhint %}

#### Sample cURL request

```bash
curl --location --request POST 'https://yourdomain.com/crm/create' \
--header 'Auth-Key: YOUR_AUTH_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
  "phone": "[CONTACT_PHONE_WITH_COUNTRY_CODE]",
  "name": "[CONTACT_NAME]",
  "email": "[CONTACT_EMAIL]",
  "custom_fields": {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3"
  }
}'
```

#### API Response

{% tabs %}
{% tab title="Success (Status 200)" %}

```json
{
    "lead_id": "[CONTACT_LEAD_ID]",
    "status": "Successfully Created"
}
```

{% endtab %}

{% tab title="Failure (Status XXX)" %}

```json
{
    "status": "Creation Failed",
    "reason": "Error message"
}
```

{% endtab %}
{% endtabs %}

### 2. Fetching an Existing Lead - CRM API

Used by QuickReply.ai to **check if a lead already exists** before creating a new one or to fetch your CRM’s `lead_id`.

#### Endpoint

```http
GET https://yourdomain.com/crm/fetch
```

{% hint style="success" %}
If your stack prefers `POST` for this, you can use `POST /crm/fetch` with the same body
{% endhint %}

#### Headers

| Name                                       | Type                 | Description                            |
| ------------------------------------------ | -------------------- | -------------------------------------- |
| Auth-Key<mark style="color:red;">\*</mark> | Authorization Header | Your shared API key / token (required) |

#### Request Body (or query parameters)

At least one of these should be accepted:

| Name     | Type   | Description                             |
| -------- | ------ | --------------------------------------- |
| phone    | string | Fetch by phone (with country code)      |
| email    | string | Fetch by email                          |
| lead\_id | string | Fetch by CRM lead ID (if already known) |

{% hint style="warning" %}
At least 1 field should be available.&#x20;
{% endhint %}

#### Sample cURL Request

```bash
curl --location --request GET 'https://yourdomain.com/crm/fetch' \
--header 'Auth-Key: YOUR_AUTH_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
  "phone": "[CONTACT_PHONE_WITH_COUNTRY_CODE]",
  "email": "[CONTACT_EMAIL]",
  "lead_id": "[CONTACT_LEAD_ID]"
}'
```

#### API Response

{% tabs %}
{% tab title="Success (Status 200)" %}

```bash
{
  "lead_id": "[CONTACT_LEAD_ID]",
  "lead_fields": {
    "phone": "[CONTACT_PHONE_WITH_COUNTRY_CODE]",
    "name": "[CONTACT_NAME]",
    "email": "[CONTACT_EMAIL]",
    "custom_fields": {
      "key1": "value1",
      "key2": "value2",
      "key3": "value3"
    }
  }
}
```

{% endtab %}

{% tab title="Failure (Status XXX)" %}

```json
{
    "status": "Not Found",
    "reason": "No matching lead"
}
```

{% endtab %}
{% endtabs %}

### 3. Updating an Existing Lead - CRM API

#### Endpoint

```http
POST https://yourdomain.com/crm/update/{{lead_id}}
```

or

```http
PUT https://yourdomain.com/crm/update/{{lead_id}}
```

#### Headers

| Name                                       | Type                 | Description                            |
| ------------------------------------------ | -------------------- | -------------------------------------- |
| Auth-Key<mark style="color:red;">\*</mark> | Authorization Header | Your shared API key / token (required) |

#### Path / Query Parameter

| Name                                       | Type   | Description                                                                                                 |
| ------------------------------------------ | ------ | ----------------------------------------------------------------------------------------------------------- |
| lead\_id<mark style="color:red;">\*</mark> | string | <p>Respective Lead ID in the CRM<br><br><em>(The same as returned in response from Fetch Lead API)</em></p> |

#### Request Body

| Name                                    | Type            | Description                                                                                                                      |
| --------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| phone<mark style="color:red;">\*</mark> | string          | <p>Phone Number of the user<br><em>(With Country Code)</em></p>                                                                  |
| name<mark style="color:red;">\*</mark>  | string          | Full Name of the user                                                                                                            |
| email                                   | string          | Email of the user                                                                                                                |
| custom\_fields                          | Key-Value Pairs | Extra Attributes which you want to sync to the CRM, where the **key-names** are the respective **schema field names** in the CRM |

{% hint style="warning" %}
Fields marked with <mark style="color:red;">\*</mark> are Required.
{% endhint %}

#### Sample cURL Request

```bash
curl --location --request POST 'https://yourdomain.com/crm/update/[CONTACT_LEAD_ID]' \
--header 'Auth-Key: YOUR_AUTH_KEY' \
--header 'Content-Type: application/json' \
--data-raw '{
  "phone": "[CONTACT_PHONE_WITH_COUNTRY_CODE]",
  "name": "[CONTACT_NAME]",
  "email": "[CONTACT_EMAIL]",
  "custom_fields": {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3"
  }
}'
```

#### API Response

{% tabs %}
{% tab title="Success (Status 200)" %}

```json
{
  "lead_id": "[CONTACT_LEAD_ID]",
  "status": "Successfully Updated"
}
```

{% endtab %}

{% tab title="Failure (Status XXX)" %}

```json
{
    "status": "Update Failed",
    "reason": "Error message"

```

{% endtab %}
{% endtabs %}

***

## Webhooks & Security Requirements

* Endpoints must be **publicly accessible HTTPS URLs**
* Accept **JSON** (recommended) or Form POST bodies
* Support a **static auth mechanism**, such as:
  * `Auth-Key` header
  * `Authorization: Bearer <token>` header
  * A shared secret in the body/query (less secure; not recommended if you can avoid it)

***

## Implementation Steps

1. **Design your CRM endpoints**
   * Decide URL structure for `create`, `fetch`, and `update`
   * Make sure they match the field structure above
2. **Implement and test locally**
   * Implement your APIs and test with curl/Postman
   * Confirm responses and error codes are correct
3. **Share details with QuickReply.ai**

   Send the following to your POC or QuickReply.ai's support:

   * Base URL and full paths for Create/Fetch/Update
   * Auth type and keys (in a secure channel)
   * Any special field mappings (e.g. `utm_campaign` → `crm_campaign_code`)
4. **QuickReply.ai configuration**
   * QuickReply.ai team configures your endpoints and mapping
   * They enable contact sync and test with sample contacts
   * Typical go-live time is around **5 business days**, depending on complexity.

***

## FAQs

<details>

<summary>Is the sync real-time?</summary>

It’s **near real-time**. By default, QuickReply.ai waits \~5 minutes after the last change before syncing to avoid too many small updates. This delay can be adjusted (shorter intervals may incur additional charges).

</details>

<details>

<summary>Can we use a single endpoint for both create and update?</summary>

Yes. As long as your endpoint can decide whether to create or update based on the payload (e.g. presence of `lead_id`, or unique `phone`/`email`), you can use a single URL.

</details>

<details>

<summary>Can the payload structure be customized to our CRM?</summary>

Yes. Field names under `custom_fields`, or even the top-level field naming, can be customized by the QuickReply.ai team to match your CRM’s schema.

</details>

<details>

<summary>What happens if our API fails (4xx/5xx)?</summary>

QuickReply.ai will treat this as a sync failure and will retry if there is new change in contact details or attributes.&#x20;

</details>

<details>

<summary>We don’t have a Fetch API. Can we still integrate?</summary>

Yes. In that case QuickReply.ai will typically only use the **Create** and **Update** APIs, and you must handle de-duplication based on phone/email on your side.

</details>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.quickreply.ai/developer/apis/external-crm-integration.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
