Reactive CRM Multi-Contract Website Lead Intake ← Integration guide

Multi-Contract Website Lead Intake

This document describes a single API contract through which the site backend sends lead data, the visitor's contact details, and marketing UTM metrics to ReactiveCRM in one request.

1. Purpose

The site integrator does not need to separately create the contact, phone, email, lead, and marketing event. One request must atomically create:

  1. the contact;
  2. the contact's primary phone;
  3. the contact's primary email, if provided;
  4. the lead;
  5. the link between the contact and the lead;
  6. the marketing event and UTM attribution.

If any step fails, the whole request is rolled back and partially created data is not saved.

2. Endpoint

http
POST /api/site/leads
Authorization: Bearer <CRM_ACCESS_TOKEN>
Content-Type: application/json

The token must be passed only from the site backend or a serverless function. Never place the CRM token in browser JavaScript.

The tenant is determined from the authorization token. All provided UUIDs are validated within this tenant.

3. Full JSON Contract

json
{
  "externalId": "site-form-01J7K9A2M4Y5T6",
  "lead": {
    "leadSourceId": "7b33b18f-55bd-48a5-a8f0-e450a56dde47",
    "quality": "WARM",
    "message": "I'd like a consultation on CRM implementation"
  },
  "contact": {
    "firstName": "Ivan",
    "lastName": "Ivanov",
    "phone": "+79991234567",
    "email": "ivan@example.com"
  },
  "marketing": {
    "occurredAt": "2026-09-06T18:00:00Z",
    "visitorId": "visitor-8b7f",
    "sessionId": "session-31ac",
    "source": "website",
    "channel": "paid",
    "utmSource": "google",
    "utmMedium": "cpc",
    "utmCampaign": "summer-consulting",
    "utmContent": "banner-a",
    "utmTerm": "crm consultation",
    "gclid": "EAIaIQobChMI-example",
    "fbclid": null,
    "landingUrl": "https://example.com/consultation",
    "referrer": "https://www.google.com/"
  }
}

4. Request Fields

4.1. Root fields

FieldTypeRequiredDescription
externalIdstringrecommendedStable unique ID of the form submission for idempotency
leadobjectyesData of the lead being created
contactobjectyesContact person data
marketingobjectnoUTM metrics and technical data of the marketing visit

externalId is generated before the first send attempt. When repeating the request after a timeout or network error, use the same value.

4.2. The lead object

FieldTypeRequiredDescription
leadSourceIduuidyesID of the lead source in the current tenant
qualitystringnoInitial rating: HOT, WARM, COOL or COLD
messagestringnoVisitor's message or a comment on the submission

If the site does not perform pre-qualification, it is better not to send the quality field.

4.3. The contact object

FieldTypeRequiredDescription
firstNamestringyesContact person's first name
lastNamestringnoContact person's last name
phonestringyesPrimary phone; E.164 format recommended
emailstring(email)noContact person's primary email

The minimum data required to process a submission is firstName and phone.

4.4. The marketing object

FieldTypeRequiredDescription
occurredAtdate-timenoForm submission time; CRM time is used when absent
visitorIdstringnoAnonymous visitor ID
sessionIdstringnoSite session ID
sourcestringnoEvent source, defaults to website
channelstringnoChannel: e.g. paid, organic, social, email, direct
utmSourcestringnoutm_source value
utmMediumstringnoutm_medium value
utmCampaignstringnoutm_campaign value
utmContentstringnoutm_content value
utmTermstringnoutm_term value
gclidstringnoGoogle Click ID
fbclidstringnoMeta/Facebook Click ID
landingUrlstringnoLanding page URL
referrerstringnoPrevious page URL

Personal data must not be duplicated in the marketing object. Name, phone, email and the message text are passed only in contact and lead.

5. Minimal Request

json
{
  "externalId": "site-form-550e8400-e29b-41d4-a716-446655440000",
  "lead": {
    "leadSourceId": "7b33b18f-55bd-48a5-a8f0-e450a56dde47"
  },
  "contact": {
    "firstName": "Ivan",
    "phone": "+79991234567"
  }
}

6. Successful Response

First request — 201 Created

json
{
  "leadId": "22222222-2222-2222-2222-222222222222",
  "contactId": "33333333-3333-3333-3333-333333333333",
  "phoneId": "44444444-4444-4444-4444-444444444444",
  "emailId": "55555555-5555-5555-5555-555555555555",
  "marketingEventId": "66666666-6666-6666-6666-666666666666",
  "duplicate": false,
  "createdAt": "2026-09-06T18:00:01Z"
}

If email or marketing data is not provided, the corresponding IDs are returned as null.

Repeat with the same externalId200 OK

json
{
  "leadId": "22222222-2222-2222-2222-222222222222",
  "contactId": "33333333-3333-3333-3333-333333333333",
  "phoneId": "44444444-4444-4444-4444-444444444444",
  "emailId": "55555555-5555-5555-5555-555555555555",
  "marketingEventId": "66666666-6666-6666-6666-666666666666",
  "duplicate": true,
  "createdAt": "2026-09-06T18:00:01Z"
}

A repeated request must not create a second lead or contact.

7. cURL Example

bash
curl -X POST "$CRM_BASE_URL/api/site/leads" \
  -H "Authorization: Bearer $CRM_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "externalId": "site-form-01J7K9A2M4Y5T6",
    "lead": {
      "leadSourceId": "7b33b18f-55bd-48a5-a8f0-e450a56dde47",
      "quality": "WARM",
      "message": "Call me back"
    },
    "contact": {
      "firstName": "Ivan",
      "lastName": "Ivanov",
      "phone": "+79991234567",
      "email": "ivan@example.com"
    },
    "marketing": {
      "utmSource": "google",
      "utmMedium": "cpc",
      "utmCampaign": "crm-demo",
      "landingUrl": "https://example.com/demo"
    }
  }'

8. TypeScript Interfaces

ts
type LeadQuality = 'HOT' | 'WARM' | 'COOL' | 'COLD';

interface SiteLeadRequest {
  externalId?: string;
  lead: {
    leadSourceId: string;
    quality?: LeadQuality;
    message?: string;
  };
  contact: {
    firstName: string;
    lastName?: string;
    phone: string;
    email?: string;
  };
  marketing?: {
    occurredAt?: string;
    visitorId?: string;
    sessionId?: string;
    source?: string;
    channel?: string;
    utmSource?: string;
    utmMedium?: string;
    utmCampaign?: string;
    utmContent?: string;
    utmTerm?: string;
    gclid?: string;
    fbclid?: string;
    landingUrl?: string;
    referrer?: string;
  };
}

interface SiteLeadResponse {
  leadId: string;
  contactId: string;
  phoneId: string;
  emailId: string | null;
  marketingEventId: string | null;
  duplicate: boolean;
  createdAt: string;
}

9. Server-Side Example

ts
export async function sendLeadToCrm(payload: SiteLeadRequest): Promise<SiteLeadResponse> {
  const response = await fetch(`${process.env.CRM_BASE_URL}/api/site/leads`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.CRM_ACCESS_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(payload),
  });

  if (!response.ok) {
    const body = await response.text();
    throw new Error(`ReactiveCRM returned ${response.status}: ${body}`);
  }

  return response.json() as Promise<SiteLeadResponse>;
}

10. Errors

HTTPCauseIntegrator action
400Format error, missing required field, invalid email/phone/qualityFix the data; do not retry automatically without changes
401Token missing or invalidRefresh the integration token
403No access to the tenant or operationCheck the integration user and role
404leadSourceId not found in the current tenantUpdate the source ID in the site settings
409externalId is already linked to an incompatible requestCheck the ID generation and the integration log
5xxTransient CRM errorRetry the same request with the same externalId

Example validation error:

json
{
  "status": 400,
  "error": "Bad Request",
  "message": "contact.phone must not be blank",
  "path": "/api/site/leads"
}

11. Idempotency

12. Mini-Guide for the Site Developer

  1. On first visit, store UTM tags, gclid, fbclid, the landing URL and the referrer.
  2. When the form is submitted, generate a stable externalId.
  3. Send the form to the site backend.
  4. The backend adds the CRM token and calls POST /api/site/leads.
  5. On 201 or 200 with duplicate: true, consider the submission delivered.
  6. On a timeout or 5xx, retry the request with the same externalId.
  7. Never send the CRM token directly from the browser.

13. What the CRM Manager Gets

After a successful request, CRM contains a lead that:

This set is enough for the manager to see the submission, call the contact, and qualify the lead.