Getting Leads from Your Website
Instructions for integrating your website form with ReactiveCRM: capturing form submissions as leads, sending marketing attribution events, and verifying the result.
1. Overview
The recommended flow consists of two requests:
- The site sends the form data to its own backend.
- The site backend creates a lead in CRM via
POST /api/leads/create. - After successful creation, the backend sends a
lead_submittedmarketing event viaPOST /api/dashboard/marketing/events, passing the receivedleadId. - CRM stores UTM tags, advertising identifiers and the referrer for marketing attribution.
Website form
|
v
Site backend / server-side proxy
| POST /api/leads/create
v
ReactiveCRM -> leadId
| POST /api/dashboard/marketing/events
v
Marketing attribution and dashboard
Do not send CRM JWT and secrets directly from the browser. Use the site backend or a serverless function so the CRM token and service identifiers are never exposed to the visitor.
2. Authentication and Base URL
All requests run in the context of the relevant tenant and require CRM authorization:
Authorization: Bearer <CRM_ACCESS_TOKEN>
Content-Type: application/json
The examples use:
CRM_BASE_URL=https://crm.example.com
Replace it with the URL of your environment.
3. Data to Collect on the Site
Lead data
For creating a lead, the API supports:
| Field | Required | Description |
|---|---|---|
message | no | Message or text of the inquiry from the form |
ownerId | yes | UUID of the CRM user responsible for the lead |
authorId | yes | UUID of the CRM user who created the lead |
clientId | no | UUID of an existing client |
contactId | no | UUID of an existing contact |
leadSourceId | no | UUID of a node in the lead-source tree |
statusId | no | UUID of the initial status |
quality | no | HOT, WARM, COOL or COLD |
ownerId and authorId are UUIDs of CRM users. For a public website form, do not let the visitor set these values themselves: the backend must supply them from the integration configuration.
Marketing visit data
Before the form is submitted, store the following in cookies, session storage, or on the backend:
utm_source→utmSourceutm_medium→utmMediumutm_campaign→utmCampaignutm_content→utmContentutm_term→utmTermgclid— Google Ads identifierfbclid— Meta/Facebook identifier- Landing page URL →
landingUrl - Previous page URL →
referrer - Your own visitor and session identifiers →
visitorId,sessionId
Save the values on the first visit and do not overwrite them with empty parameters while navigating within the site.
4. Creating a Lead
Endpoint
POST /api/leads/create
Example request
curl -X POST "$CRM_BASE_URL/api/leads/create" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"message": "Website inquiry: consultation request",
"ownerId": "11111111-1111-1111-1111-111111111111",
"authorId": "11111111-1111-1111-1111-111111111111",
"quality": "WARM"
}'
Example 201 Created response
{
"id": "22222222-2222-2222-2222-222222222222",
"message": "Website inquiry: consultation request",
"quality": "WARM",
"dealId": null,
"createdAt": "2026-09-05T20:00:00Z",
"updatedAt": "2026-09-05T20:00:00Z",
"version": 0,
"author": {
"id": "11111111-1111-1111-1111-111111111111",
"firstName": "CRM",
"lastName": "Bot"
},
"owner": {
"id": "11111111-1111-1111-1111-111111111111",
"firstName": "CRM",
"lastName": "Bot"
}
}
Save the response id: this value is passed in the leadId field of the marketing event.
5. Sending the Lead-Submitted Event
Endpoint
POST /api/dashboard/marketing/events
Example request
curl -X POST "$CRM_BASE_URL/api/dashboard/marketing/events" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"eventType": "lead_submitted",
"occurredAt": "2026-09-05T20:00:00Z",
"visitorId": "visitor-8b7f",
"sessionId": "session-31ac",
"leadId": "22222222-2222-2222-2222-222222222222",
"externalId": "site-form-submit-20260905-000123",
"source": "website",
"channel": "paid",
"utmSource": "google",
"utmMedium": "cpc",
"utmCampaign": "summer-consulting",
"utmContent": "banner-a",
"utmTerm": "crm consultation",
"gclid": "EAIaIQobChMI-example",
"landingUrl": "https://www.example.com/consultation",
"referrer": "https://www.google.com/",
"metadata": {
"formName": "consultation",
"pageType": "landing"
}
}'
Example response
{
"accepted": true,
"duplicate": false,
"eventId": "33333333-3333-3333-3333-333333333333"
}
If the same externalId has already been processed for this source and tenant pair, the API returns a success response with duplicate: true. Treat this as a success, not an error, and do not create a second lead.
6. Server-Side Handler Example
Below is a simplified Node.js example. Form data must go to the site backend, not directly to CRM from the browser.
async function createWebsiteLead(form, attribution) {
const headers = {
Authorization: `Bearer ${process.env.CRM_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
};
const leadResponse = await fetch(
`${process.env.CRM_BASE_URL}/api/leads/create`,
{
method: 'POST',
headers,
body: JSON.stringify({
message: form.message || `Website inquiry: ${form.subject || 'no subject'}`,
ownerId: process.env.CRM_LEAD_OWNER_ID,
authorId: process.env.CRM_LEAD_AUTHOR_ID,
quality: 'WARM',
}),
},
);
if (!leadResponse.ok) {
throw new Error(`CRM lead creation failed: ${leadResponse.status}`);
}
const lead = await leadResponse.json();
const externalId = `website-${form.requestId}`;
const eventResponse = await fetch(
`${process.env.CRM_BASE_URL}/api/dashboard/marketing/events`,
{
method: 'POST',
headers,
body: JSON.stringify({
eventType: 'lead_submitted',
occurredAt: new Date().toISOString(),
visitorId: attribution.visitorId,
sessionId: attribution.sessionId,
leadId: lead.id,
externalId,
source: 'website',
channel: attribution.channel || 'direct',
utmSource: attribution.utmSource,
utmMedium: attribution.utmMedium,
utmCampaign: attribution.utmCampaign,
utmContent: attribution.utmContent,
utmTerm: attribution.utmTerm,
gclid: attribution.gclid,
fbclid: attribution.fbclid,
landingUrl: attribution.landingUrl,
referrer: attribution.referrer,
metadata: { formName: form.formName || 'website' },
}),
},
);
if (!eventResponse.ok) {
// The lead is already created. Queue the event and retry it
// with the same externalId instead of creating a second lead.
throw new Error(`CRM marketing event failed: ${eventResponse.status}`);
}
return { leadId: lead.id, marketingEvent: await eventResponse.json() };
}
7. Idempotency and Retries
- Generate a stable
externalIdfor each form submission. For example, use a UUID created before the first request, or a unique formrequestId. - On a network error, retry the marketing-event request with the same
externalId. - Do not generate a new
externalIdon retry: that would create a duplicate event. - If the lead-creation request ends with an unknown result due to a timeout, do not blindly create a new lead. First use your own
requestIdand a deduplication mechanism on the site backend, or find the created lead in CRM. - An event-sending error must not cause the user to resubmit the form without checking the lead-creation result.
8. Personal Data and metadata
Passing personal data and form contents in metadata is forbidden:
- phone
- first and last name
- address
- message text
- cookies with identifiers containing personal data
Personal data must be passed only in the CRM entities intended for it — for example, in a pre-created contactId/clientId. Keep only technical attributes in metadata: form name, page type, banner variant, and so on.
9. Typical Responses and Errors
| HTTP | Situation | What to do |
|---|---|---|
201 | Lead created | Save the id and send lead_submitted |
200 + duplicate: false | Event accepted | Save the eventId in the integration log |
200 + duplicate: true | Event was already accepted | Treat as processed; do not send again |
400 | Invalid data or PII in metadata | Fix the payload; retrying without changes will not help |
401 | Invalid or missing token | Refresh the CRM token on the backend |
403 | Token lacks permission for the operation | Check role and tenant |
5xx / timeout | Transient CRM or network error | Retry with the same externalId; for leads use a queue |
10. Verifying Results in CRM
After integration, verify:
- The lead appears via
GET /api/leads/{id}or in the listGET /api/leads/paged. - The lead's owner, author, and creation time are correct.
- The marketing-event response has
duplicateequal tofalseon the first send. - Re-sending the same submission returns
duplicate: true. - In the marketing dashboard, the data appears in the right period, channel, and campaign.
Fetching the created lead
curl "$CRM_BASE_URL/api/leads/$LEAD_ID" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN"
Fetching leads for a list or reconciliation
curl "$CRM_BASE_URL/api/leads/paged?page=0&size=20&createdAtFrom=2026-09-05T00:00:00Z&createdAtTo=2026-09-05T23:59:59Z&sort=createdAt,desc" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN"
The page parameter starts at 0; the default size is 20. To fetch website leads, additionally use the message, createdAtFrom/createdAtTo, contactEmail, or search filters if the relevant data is already stored in CRM.
Reactive CRM