> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vambe.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Upsert a contact

> Creates or finds an existing contact for whatsapp, web-whatsapp, or webchat channels. For whatsapp/web-whatsapp, the contact is identified by phone number. For webchat, the contact is identified by external_user_id. Optionally assigns an agent, updates metadata, sets a stage, and upserts custom field values on the associated customer.

## Overview

Create or update a customer contact with intelligent AI-powered data processing. This endpoint automatically creates the contact if it doesn't exist, or updates it if it does, along with optional metadata, custom field values, stage assignment, and agent assignment - all in a single request.

Supports **WhatsApp**, **Web-WhatsApp**, and **Webchat** channels. For WhatsApp-based channels the contact is identified by phone number; for webchat, the contact is identified by an external user ID you provide.

## Use Cases

* **CRM Data Import**: Bulk import customer data from external CRM systems
* **Form Submissions**: Create customers from web form submissions with flexible data
* **E-commerce Integration**: Sync customer data from your e-commerce platform
* **Lead Capture**: Capture leads with any structure of data
* **Customer Onboarding**: Create customer profiles during signup flows
* **Data Migration**: Migrate customer data from legacy systems
* **Webchat User Tracking**: Create contacts for authenticated webchat users with your own external IDs

## Authentication

This endpoint requires authentication using an API key. Include your API key in the request header:

```
x-api-key: your_api_key_here
```

## Request Body

### Common Fields

| Field                 | Type   | Required | Description                                                  |
| --------------------- | ------ | -------- | ------------------------------------------------------------ |
| `channel`             | string | Yes      | Channel type: `"whatsapp"`, `"web-whatsapp"`, or `"webchat"` |
| `contact_name`        | string | No       | Customer's display name                                      |
| `contact_email`       | string | No       | Customer's email address                                     |
| `stageId`             | string | No       | Pipeline stage UUID to assign the customer to                |
| `agentId`             | string | No       | Team member UUID to assign as responsible agent              |
| `meta_data`           | object | No       | Any additional customer data (AI will structure it)          |
| `custom_field_values` | array  | No       | Custom field values to set on the customer (see below)       |

### WhatsApp / Web-WhatsApp Fields

Required when `channel` is `"whatsapp"` or `"web-whatsapp"`:

| Field                  | Type   | Required | Description                                     |
| ---------------------- | ------ | -------- | ----------------------------------------------- |
| `channel_phone_number` | string | Yes      | Your WhatsApp channel phone number (the sender) |
| `contact_phone_number` | string | Yes      | Customer's phone number                         |

### Webchat Fields

Required when `channel` is `"webchat"`:

| Field              | Type   | Required | Description                                                              |
| ------------------ | ------ | -------- | ------------------------------------------------------------------------ |
| `channel_id`       | string | Yes      | UUID of your webchat channel                                             |
| `external_user_id` | string | Yes      | Your unique identifier for the user (used to find or create the contact) |

### Custom Field Values

The `custom_field_values` array lets you set values on the associated customer using custom field definition keys. Each entry has:

| Field   | Type   | Required | Description                     |
| ------- | ------ | -------- | ------------------------------- |
| `key`   | string | Yes      | The custom field definition key |
| `value` | string | Yes      | The value to set                |

If a custom field definition is marked as an **identifier**, the value will participate in customer deduplication automatically.

```json theme={null}
"custom_field_values": [
  { "key": "rut", "value": "12345678-9" },
  { "key": "company_name", "value": "Acme Corp" }
]
```

## Response Structure

Returns the created or updated AI contact object:

| Field              | Type          | Description                                        |
| ------------------ | ------------- | -------------------------------------------------- |
| `id`               | string (UUID) | Unique identifier for the contact                  |
| `name`             | string        | Contact's name                                     |
| `phone`            | string        | Contact's phone number                             |
| `email`            | string        | Contact's email                                    |
| `platform`         | string        | Channel platform (whatsapp, web-whatsapp, webchat) |
| `client_id`        | string (UUID) | Your organization ID                               |
| `ai_customer_id`   | string (UUID) | Associated customer profile ID                     |
| `created_at`       | string (ISO)  | Contact creation timestamp                         |
| `default_stage_id` | string (UUID) | Current pipeline stage (if assigned)               |

## Example Requests

### WhatsApp Contact

```bash theme={null}
curl --request POST \
  'https://api.vambe.me/api/public/customer/upsert/info' \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: your_api_key_here' \
  --data-raw '{
    "channel_phone_number": "+56987654321",
    "channel": "whatsapp",
    "contact_phone_number": "+56912345678",
    "contact_name": "Maria Rodriguez",
    "contact_email": "maria.rodriguez@example.com",
    "stageId": "550e8400-e29b-41d4-a716-446655440000",
    "agentId": "228d7a0d-9072-4ca8-939b-959b75cc606a",
    "meta_data": {
      "company": "Acme Corp",
      "position": "CEO",
      "industry": "Technology",
      "source": "LinkedIn Campaign"
    },
    "custom_field_values": [
      { "key": "company_name", "value": "Acme Corp" },
      { "key": "rut", "value": "12345678-9" }
    ]
  }'
```

### Webchat Contact

```bash theme={null}
curl --request POST \
  'https://api.vambe.me/api/public/customer/upsert/info' \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: your_api_key_here' \
  --data-raw '{
    "channel": "webchat",
    "channel_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "external_user_id": "user-abc-123",
    "contact_name": "Jane Doe",
    "contact_email": "jane@example.com",
    "custom_field_values": [
      { "key": "plan", "value": "premium" },
      { "key": "account_id", "value": "ACC-9876" }
    ]
  }'
```

## Example Response

```json theme={null}
{
  "id": "df980fc8-b6db-4820-bf22-2969482d106d",
  "name": "Maria Rodriguez",
  "phone": "+56912345678",
  "email": "maria.rodriguez@example.com",
  "platform": "whatsapp",
  "client_id": "550e8400-e29b-41d4-a716-446655440000",
  "ai_customer_id": "660e8400-e29b-41d4-a716-446655440001",
  "created_at": "2024-09-30T10:00:00.000Z",
  "default_stage_id": "550e8400-e29b-41d4-a716-446655440000",
  "blocked": false,
  "chat_status": "UNATTENDED",
  "is_chat_read": false
}
```

## Common Use Cases

### 1. Import CRM Data with Custom Fields

```javascript theme={null}
const importCRMCustomer = async (crmData) => {
  const response = await fetch(
    'https://api.vambe.me/api/public/customer/upsert/info',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({
        channel_phone_number: '+56987654321',
        channel: 'whatsapp',
        contact_name: crmData.full_name,
        contact_phone_number: crmData.phone,
        contact_email: crmData.email,
        stageId: 'your_initial_stage_id',
        custom_field_values: [
          { key: 'company_name', value: crmData.company_name },
          { key: 'industry', value: crmData.industry_sector },
          { key: 'lead_score', value: String(crmData.qualification_score) },
        ],
        meta_data: {
          source: crmData.lead_source,
          notes: crmData.internal_notes,
        },
      }),
    },
  );

  const contact = await response.json();
  console.log(`Imported customer: ${contact.name} (${contact.id})`);
  return contact;
};
```

### 2. Create Webchat Contact from Authenticated User

```javascript theme={null}
const createWebchatContact = async (authenticatedUser) => {
  const response = await fetch(
    'https://api.vambe.me/api/public/customer/upsert/info',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({
        channel: 'webchat',
        channel_id: 'your_webchat_channel_uuid',
        external_user_id: authenticatedUser.id,
        contact_name: authenticatedUser.displayName,
        contact_email: authenticatedUser.email,
        custom_field_values: [
          { key: 'plan', value: authenticatedUser.plan },
          { key: 'account_id', value: authenticatedUser.accountId },
        ],
      }),
    },
  );

  return await response.json();
};
```

### 3. Process Form Submission

```javascript theme={null}
const processContactForm = async (formData) => {
  const response = await fetch(
    'https://api.vambe.me/api/public/customer/upsert/info',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({
        channel_phone_number: process.env.WHATSAPP_PHONE,
        channel: 'whatsapp',
        contact_name: formData.name,
        contact_phone_number: formData.phone,
        contact_email: formData.email,
        stageId: 'your_leads_stage_id',
        agentId: formData.assignToAgent || null,
        meta_data: {
          formType: 'Contact Form',
          submittedAt: new Date().toISOString(),
          subject: formData.subject,
          message: formData.message,
          source: formData.utm_source,
          campaign: formData.utm_campaign,
        },
      }),
    },
  );

  return await response.json();
};
```

### 4. E-commerce Customer Sync

```javascript theme={null}
const syncEcommerceCustomer = async (order) => {
  const response = await fetch(
    'https://api.vambe.me/api/public/customer/upsert/info',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'your_api_key_here',
      },
      body: JSON.stringify({
        channel_phone_number: '+56987654321',
        channel: 'whatsapp',
        contact_name: `${order.customer.first_name} ${order.customer.last_name}`,
        contact_phone_number: order.customer.phone,
        contact_email: order.customer.email,
        stageId: 'your_customers_stage_id',
        meta_data: {
          orderId: order.id,
          orderNumber: order.order_number,
          orderTotal: order.total_price,
          currency: order.currency,
          products: order.line_items.map((item) => item.title).join(', '),
        },
      }),
    },
  );

  return await response.json();
};
```

### 5. Bulk Customer Import

```javascript theme={null}
const bulkImportCustomers = async (customers) => {
  const results = [];

  for (const customer of customers) {
    try {
      const response = await fetch(
        'https://api.vambe.me/api/public/customer/upsert/info',
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'x-api-key': 'your_api_key_here',
          },
          body: JSON.stringify({
            channel_phone_number: process.env.WHATSAPP_PHONE,
            channel: 'whatsapp',
            contact_name: customer.name,
            contact_phone_number: customer.phone,
            contact_email: customer.email,
            custom_field_values: customer.customFields || [],
            meta_data: customer.additionalData,
          }),
        },
      );

      const result = await response.json();
      results.push({ phone: customer.phone, success: true, id: result.id });
    } catch (error) {
      results.push({
        phone: customer.phone,
        success: false,
        error: error.message,
      });
    }

    // Add delay to avoid rate limiting
    await new Promise((resolve) => setTimeout(resolve, 200));
  }

  console.log(
    `Imported ${results.filter((r) => r.success).length} of ${customers.length} customers`,
  );

  return results;
};
```

## Key Features

### Upsert Behavior

The contact lookup depends on the channel:

| Channel        | Identified by                                |
| -------------- | -------------------------------------------- |
| `whatsapp`     | `contact_phone_number` on the given channel  |
| `web-whatsapp` | `contact_phone_number` on the given channel  |
| `webchat`      | `external_user_id` on the given `channel_id` |

* **If contact exists**: Returns the existing contact and applies any updates (name, agent, stage, metadata, custom fields)
* **If contact doesn't exist**: Creates a new contact and customer
* **Idempotent**: Safe to call multiple times with the same data

### Custom Field Values

* Values are set on the **customer** (not the contact), so they are shared across all contacts for that customer
* Fields are identified by their definition **key** (e.g. `"rut"`, `"company_name"`)
* If a field definition is marked as an **identifier**, the value participates in automatic customer deduplication
* All custom field definition keys must exist beforehand; the endpoint returns a `400` error listing any missing keys
* Supported field types: `text`, `number`, `date`, `boolean`, `options`

### AI-Powered Data Processing

The `meta_data` field uses AI to:

* **Extract structured data** from unstructured metadata
* **Match fields** to your custom field definitions
* **Convert data types** automatically
* **Handle flexible formats** for phone numbers, dates, etc.

### Automatic Processing

All in one request:

1. Create/update contact
2. Assign to pipeline stage
3. Assign to agent
4. Process and save metadata
5. Upsert custom field values

## Channel Types

| Channel        | Description            | Required Fields                                |
| -------------- | ---------------------- | ---------------------------------------------- |
| `whatsapp`     | WhatsApp Business API  | `channel_phone_number`, `contact_phone_number` |
| `web-whatsapp` | WhatsApp Web (QR Code) | `channel_phone_number`, `contact_phone_number` |
| `webchat`      | Webchat widget         | `channel_id`, `external_user_id`               |

## Error Responses

| Status Code | Description                                                                         |
| ----------- | ----------------------------------------------------------------------------------- |
| 400         | Bad Request - Missing required fields for the channel, or invalid custom field keys |
| 401         | Unauthorized - Invalid or missing API key                                           |
| 500         | Internal Server Error - Something went wrong                                        |

## Important Notes

* **Channel Phone Number**: Must be a WhatsApp number/channel you own and have configured
* **Channel ID**: Must be a webchat channel UUID you own
* **External User ID**: Your own unique identifier for the webchat user; used to deduplicate contacts
* **Phone Format**: Automatically cleaned (spaces, dashes, etc. are removed)
* **Custom Field Keys**: Must match existing custom field definitions; create definitions first in the Vambe dashboard
* **AI Processing**: Metadata is processed asynchronously with AI
* **Immediate Response**: Returns contact immediately, metadata processing continues in background

## Best Practices

### 1. Use Custom Fields for Structured Data

Prefer `custom_field_values` over `meta_data` when you have well-defined fields. Custom fields are typed, searchable, and can be marked as identifiers for deduplication.

```javascript theme={null}
// Preferred: structured custom fields
{
  "custom_field_values": [
    { "key": "company_name", "value": "Acme Corp" },
    { "key": "rut", "value": "12345678-9" },
    { "key": "plan", "value": "enterprise" }
  ]
}

// Use meta_data for unstructured/dynamic data
{
  "meta_data": {
    "notes": "Met at conference, interested in premium plan",
    "source": "Trade Show 2025"
  }
}
```

### 2. Include Source Information

```javascript theme={null}
meta_data: {
  source: 'LinkedIn Campaign',
  importedAt: new Date().toISOString(),
  importedBy: 'CRM Sync Script',
}
```

### 3. Validate Data Before Sending

```javascript theme={null}
const validateAndUpsert = async (customerData) => {
  if (!customerData.phone || !customerData.name) {
    throw new Error('Phone and name are required');
  }

  const cleanPhone = customerData.phone.replace(/[^\d+]/g, '');

  return await fetch('https://api.vambe.me/api/public/customer/upsert/info', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-api-key': 'your_api_key_here',
    },
    body: JSON.stringify({
      channel_phone_number: process.env.WHATSAPP_PHONE,
      channel: 'whatsapp',
      contact_phone_number: cleanPhone,
      contact_name: customerData.name,
      contact_email: customerData.email,
      custom_field_values: customerData.customFields || [],
      meta_data: customerData.additionalData,
    }),
  }).then((r) => r.json());
};
```

## Related Endpoints

* [POST /api/public/contact/{aiContactId}/update-metadata](/reference/contact/update-contact-metadata) - Update metadata for existing contact
* [POST /api/public/ticket/open/web-whatsapp/{phoneId}](/reference/ticket/open-ticket-web-whatsapp) - Create ticket for contact
* [GET /api/public/contact/{aiContactId}/info](/reference/contact/get-info-of-an-ai-contact) - Get contact details

## Migration from Deprecated Endpoint

If you're migrating from the deprecated `POST /api/public/whatsapp/message/note/send` endpoint:

**Old**:

```javascript theme={null}
POST /api/public/whatsapp/message/note/send
{
  "to_phone": "+56912345678",
  "from_phone": "your_phone",
  "note": "Internal note",
  "contact_name": "John Doe",
  "meta_data": { ... }
}
```

**New** (This endpoint):

```javascript theme={null}
POST /api/public/customer/upsert/info
{
  "channel_phone_number": "your_phone",
  "channel": "whatsapp",
  "contact_phone_number": "+56912345678",
  "contact_name": "John Doe",
  "meta_data": {
    "internal_note": "Internal note",
    ...
  }
}
```

The new endpoint provides better structure and automatic AI processing of all metadata!


## OpenAPI

````yaml post /api/public/customer/upsert/info
openapi: 3.0.0
info:
  title: Vambe AI API
  description: Vambe AI documentation
  version: '1.0'
  contact: {}
servers:
  - url: https://api.vambe.me
    description: Production Server
security: []
tags:
  - name: Vambe AI
    description: ''
paths:
  /api/public/customer/upsert/info:
    post:
      tags:
        - Customer
      summary: Upsert a contact
      description: >-
        Creates or finds an existing contact for whatsapp, web-whatsapp, or
        webchat channels. For whatsapp/web-whatsapp, the contact is identified
        by phone number. For webchat, the contact is identified by
        external_user_id. Optionally assigns an agent, updates metadata, sets a
        stage, and upserts custom field values on the associated customer.
      operationId: PublicCustomerController_createUserContact
      parameters:
        - name: x-api-key
          in: header
          description: API key needed to authorize the request
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpsertCustomerBodyDto'
            examples:
              whatsapp:
                summary: WhatsApp contact
                value:
                  channel: whatsapp
                  channel_phone_number: '+56912345678'
                  contact_phone_number: '+56987654321'
                  contact_name: John Doe
                  contact_email: john@example.com
                  agentId: 00000000-0000-0000-0000-000000000001
                  meta_data:
                    source: landing-page
                  custom_field_values:
                    - key: company_name
                      value: Acme Corp
              webchat:
                summary: Webchat contact
                value:
                  channel: webchat
                  channel_id: 00000000-0000-0000-0000-000000000002
                  external_user_id: user-abc-123
                  contact_name: Jane Doe
                  contact_email: jane@example.com
                  custom_field_values:
                    - key: rut
                      value: 12345678-9
                    - key: plan
                      value: premium
      responses:
        '201':
          description: Contact created or found successfully.
        '400':
          description: >-
            Bad request. Missing required fields for the given channel, or
            custom field definition keys not found.
        '401':
          description: Unauthorized. Invalid API key.
components:
  schemas:
    UpsertCustomerBodyDto:
      type: object
      properties:
        channel_phone_number:
          description: >-
            Phone number or ID of the channel. Required for
            whatsapp/web-whatsapp.
          type: string
          minLength: 1
        channel_id:
          description: UUID of the webchat channel. Required for webchat.
          type: string
          format: uuid
          pattern: >-
            ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
        channel:
          description: Channel type for the contact.
          type: string
          enum:
            - whatsapp
            - web-whatsapp
            - webchat
        stageId:
          description: Stage ID to assign to the contact.
          type: string
          format: uuid
          pattern: >-
            ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
        agentId:
          description: Agent ID to assign to the contact.
          type: string
          format: uuid
          pattern: >-
            ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
        contact_phone_number:
          description: Phone number of the contact. Required for whatsapp/web-whatsapp.
          type: string
        external_user_id:
          description: >-
            External user identifier used to find or create the webchat contact.
            Required for webchat.
          type: string
          minLength: 1
        contact_email:
          description: Email of the contact.
          type: string
        contact_name:
          description: Display name of the contact.
          type: string
        meta_data:
          description: Arbitrary metadata to set on the contact.
          type: object
          additionalProperties: true
        custom_field_values:
          description: >-
            Custom field values to upsert on the customer, identified by
            definition key.
          type: array
          items:
            type: object
            properties:
              key:
                description: The custom field definition key.
                type: string
                minLength: 1
              value:
                description: The value to set.
                type: string
            required:
              - key
              - value
      required:
        - channel
        - meta_data

````