Opt-Ins & Submit API Reference
POST external lead submission data directly into Hermon, and query historical traffic records, UTM attributions, logs, and form submissions.
Both bounds are inclusive. Only created_at is filterable on the list endpoint (there is no updated_at on opt-ins).
Submit Endpoint
Accepts both full-access and opt-in only API keys. Rate limited to 5 requests per second per API key. On a 429, wait the seconds in the Retry-After header and retry with the same external_reference.
Headers
Request Body Fields
| Field | Type | Required | Description |
|---|---|---|---|
| string | Required | Lead's email address | |
| first_name | string | Optional | Lead's first name. If omitted, a new lead's first name is derived from the email prefix; an existing lead's name is never overwritten by an omitted value |
| source | string | Required | One of: CALENDLY, ICLOSED, GOHIGHLEVEL, MANUAL, TYPEFORM, WEBINAR, NEWSLETTER, LANDING_PAGE, OTHER |
| last_name | string | Optional | Lead's last name |
| phone_e164 | string | Optional | Phone in E.164 format (e.g. +14155552671) |
| phone_country | string | Optional | ISO country code. phone_e164 and phone_country must be sent together |
| provider_form_id | string | Optional | Your form's unique identifier |
| provider_form_name | string | Optional | Your form's display name |
| external_reference | string | Optional | Your unique submission ID for idempotency (unique per source) |
| questions_answers | array | Optional | Q&A pairs from your form (max 50). Each item: question (string, required, max 1000 chars), answer (string, required, max 5000 chars), position (integer, optional — defaults to the item index) |
| traffic | object | Optional | UTM parameters, referrer, landing page, Meta click IDs (fbclid, fbc, fbp) and the tracking snippet public_key. Each value max 500 chars |
| user_agent | string | Optional | Visitor's browser user agent (the tracking snippet's hermon_ua field). Max 1000 chars. When omitted, the request's User-Agent header is used |
| company | object | Optional | Company details saved on the lead. Every field inside is optional; see the company object |
questions_answers item
{
"question": "What is your biggest challenge?",
"answer": "Generating consistent leads",
"position": 0
}traffic object
{
"utm_source": "facebook",
"utm_medium": "paid",
"utm_campaign": "q1-coaching",
"utm_content": "video-ad-1",
"utm_term": "coaching program",
"referrer": "https://facebook.com",
"landing_page": "https://yoursite.com/apply?fbclid=IwAR0abc123",
"fbclid": "IwAR0abc123",
"fbc": "fb.1.1736937000000.IwAR0abc123",
"fbp": "fb.1.1736937000000.1098115397",
"public_key": "pk_live_0123456789abcdef0123456789abcdef"
}- fbclid — the raw Meta click ID from the ad URL. If you do not send it explicitly but landing_page contains an fbclid query parameter, Hermon extracts it from that URL.
- fbc — the value of the Meta _fbc cookie. When sent it is stored as-is. When only fbclidis available, Hermon builds it in Meta's required fb.1.<timestamp_ms>.<fbclid> format using the opt-in time.
- fbp — the value of the Meta _fbp cookie, stored as submitted.
company object
Company details submitted alongside the opt-in. They are saved on the lead and appear in its Company Details panel; they are not stored separately on the opt-in record. The whole object is optional, so existing integrations are unaffected.
{
"business_name": "Elektrotechnisch Bureau Takken",
"vat_number": "NL123456789B01",
"address": "Voorbeeldstraat 1",
"city": "Amsterdam",
"zip_code": "1011AB",
"state_province": "Noord-Holland",
"country": "NL"
}| Field | Type | Required | Description |
|---|---|---|---|
| business_name | string | Optional | Registered company name. Max 255 characters. |
| vat_number | string | Optional | VAT number, stored as submitted (trimmed and uppercased). Not format-checked and not validated against VIES. Max 50 characters. |
| address | string | Optional | Street address. Max 255 characters. |
| city | string | Optional | City. Max 100 characters. |
| zip_code | string | Optional | ZIP / postal code. Max 20 characters. |
| state_province | string | Optional | State / province. Max 100 characters. |
| country | string | Optional | ISO 3166-1 alpha-2 country code (e.g. NL, US). Case-insensitive; an unknown code is rejected with a 400. |
Company details are also ignored on an idempotent replay — when the same external_reference + source is submitted again, Hermon returns the existing opt-in without applying any field from the repeat request.
Request example with company details
{
"email": "info@etbtakken.nl",
"first_name": "Jan",
"source": "LANDING_PAGE",
"company": {
"business_name": "Elektrotechnisch Bureau Takken",
"vat_number": "NL123456789B01",
"address": "Voorbeeldstraat 1",
"city": "Amsterdam",
"zip_code": "1011AB",
"state_province": "Noord-Holland",
"country": "NL"
}
}Submit Responses
{
"status": "success",
"message": "Lead created via opt-in",
"data": {
"opt_in_id": "uuid",
"lead_id": "uuid",
"is_new_lead": true
}
}{
"status": "success",
"message": "Opt-in recorded for existing lead",
"data": {
"opt_in_id": "uuid",
"lead_id": "uuid",
"is_new_lead": false
}
}{
"status": "error",
"message": "Invalid or revoked API key"
}{
"status": "error",
"message": "source must be one of: CALENDLY, ICLOSED, GOHIGHLEVEL, MANUAL, TYPEFORM, WEBINAR, NEWSLETTER, LANDING_PAGE, OTHER"
}Company details are validated the same way — an unknown country code or a wrong type names the offending field. Nothing is written when validation fails: no lead, opt-in or company data is created.
{
"status": "error",
"message": "company.country must be an ISO 3166-1 alpha-2 country code (e.g. NL, US)"
}{
"status": "error",
"message": "API key rate limit exceeded. Maximum 5 requests per second."
}{
"status": "error",
"message": "This submission reference is already linked to a different lead. If this is a new lead, resubmit without an external_reference or with a unique one."
}Idempotency
Pass external_reference with a unique ID from your system. If the same external_reference + source combination is submitted again, Hermon returns the existing opt-in record without creating a duplicate. If that external_reference is already linked to a lead with a different email, the request is rejected with 409.
{
"email": "john@example.com",
"first_name": "John",
"source": "LANDING_PAGE",
"external_reference": "typeform-submission-abc123"
}List Opt-Ins Records
List Opt-Ins
Retrieve a paginated list of opt-ins. Results can be filtered by source, lead ID, and creation date.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| page | integer | Optional | Page number for pagination. Default: 1 |
| limit | integer | Optional | Number of items per page. Default: 10, Max: 100 |
| source | string | Optional | Filter by opt-in source. See Opt-in Source Values. |
| lead_id | string (UUID) | Optional | UUID of a specific lead. Returns opt-ins linked to that lead. |
| created_from | string | Optional | Lower bound for created_at. Accepts YYYY-MM-DD or ISO 8601. |
| created_to | string | Optional | Upper bound for created_at. Accepts YYYY-MM-DD or ISO 8601. |
Code Examples
curl -X GET "https://api.hermon.io/api/external/opt-ins?page=1&limit=10&source=CALENDLY&lead_id=e799c30f-9da7-4fd7-9552-83b96a9bfac2" \
-H "Content-Type: application/json" \
-H "x-api-key: sk_live_your_key_here"Response Example
{
"status": "success",
"message": "Opt-ins fetched successfully",
"data": [
{
"id": "optin_e5f6a7b8-c9d0-1234-efgh-345678901234",
"lead_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"source": "TYPEFORM",
"provider_form_id": "form_abc123def456",
"provider_form_name": "Coaching Application Form",
"external_reference": "TYPEFORM-response-abc123",
"created_at": "2026-01-15T10:30:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1,
"hasNext": false,
"hasPrev": false
}
}Get Opt-In Details
Fetch detailed information for a single opt-in submission record by its unique UUID.
Path Parameters
Opt-In Object Fields
| Field | Type | Description |
|---|---|---|
| id | string (UUID) | Unique identifier for the opt-in. |
| lead_id | string (UUID) | ID of the lead this opt-in created or matched. |
| source | string (enum) | The system or integration that captured the opt-in. See Opt-in Source Values. |
| provider_form_id | string | null | The form's identifier in the originating provider (e.g. Typeform form ID). null for manual entries. |
| provider_form_name | string | null | The form's display name in the originating provider. null for manual entries. |
| external_reference | string | null | The provider's unique reference for this specific submission (e.g. Typeform response ID). |
| created_at | string (ISO 8601) | UTC timestamp when the opt-in was captured. |
| question_answers | array | Ordered list of form question/answer pairs. See Question Answer Object [Details API Only]. |
| traffic_attribution | object | UTM, referrer, and Meta click ID data. See Traffic Attribution Object [Details API Only]. |
Code Examples
curl -X GET "https://api.hermon.io/api/external/opt-ins/522b1759-d354-472c-8fb2-1a9bbc69eb9b" \
-H "Content-Type: application/json" \
-H "x-api-key: sk_live_your_key_here"Response Example
{
"status": "success",
"message": "Opt-in fetched successfully",
"data": {
"id": "optin_e5f6a7b8-c9d0-1234-efgh-345678901234",
"lead_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"source": "TYPEFORM",
"provider_form_id": "form_abc123def456",
"provider_form_name": "Coaching Application Form",
"external_reference": "TYPEFORM-response-abc123",
"created_at": "2026-01-15T10:30:00.000Z",
"question_answers": [
{
"id": "qa_001",
"question": "What is your biggest business challenge right now?",
"answer": "Struggling to convert leads into high-ticket clients consistently",
"position": 1
},
{
"id": "qa_002",
"question": "What is your monthly revenue?",
"answer": "$10,000 - $25,000",
"position": 2
},
{
"id": "qa_003",
"question": "How did you hear about us?",
"answer": "Facebook Ad",
"position": 3
}
],
"traffic_attribution": {
"utm_source": "facebook",
"utm_medium": "paid_social",
"utm_campaign": "q1-2026-coaching",
"utm_content": "video-ad-v2",
"utm_term": null,
"referrer": "https://www.facebook.com/",
"landing_page": "https://example.com/apply?fbclid=IwAR0abc123",
"fbclid": "IwAR0abc123",
"fbc": "fb.1.1736937000000.IwAR0abc123",
"fbp": "fb.1.1736937000000.1098115397"
}
}
}Values Reference
Opt-in Source Values
| Value | Description |
|---|---|
| CALENDLY | Calendly form/event |
| ICLOSED | iClosed integration |
| GOHIGHLEVEL | GoHighLevel form |
| MANUAL | Manually created |
| TYPEFORM | Typeform submission |
| WEBINAR | Webinar registration |
| NEWSLETTER | Newsletter signup |
| LANDING_PAGE | Landing page form |
| OTHER | Other source |
Nested Objects Reference
Question Answer Object Fields
| Field | Type | Description |
|---|---|---|
| id | string | Unique identifier for this question/answer pair. |
| question | string | The form question text. |
| answer | string | The respondent's answer. |
| position | integer | Display order, ascending. Opt-ins submitted without a position use the 0-based item index. |
Traffic Attribution Object Fields
| Field | Type | Description |
|---|---|---|
| utm_source | string | null | UTM source (e.g. "facebook", "google"). |
| utm_medium | string | null | UTM medium (e.g. "paid_social", "email"). |
| utm_campaign | string | null | UTM campaign name. |
| utm_content | string | null | UTM content variant. |
| utm_term | string | null | UTM keyword term. |
| referrer | string | null | HTTP referrer URL (the page that linked to the form). |
| landing_page | string | null | URL of the landing page the visitor was on when they submitted the form. |
| fbclid | string | null | Meta click ID captured at opt-in. Taken from the fbclid field, or extracted from the fbclid query parameter of landing_page when not sent explicitly. |
| fbc | string | null | Meta click cookie value in fb.1.<timestamp_ms>.<fbclid> format. Stored as-is when the _fbc cookie is forwarded; otherwise built from fbclid using the opt-in time. |
| fbp | string | null | Meta browser ID (the _fbp cookie value), stored as submitted. |
Management Errors
{
"status": "error",
"message": "Unknown query parameter(s): status"
}{
"status": "error",
"message": "Invalid or revoked API key"
}{
"status": "error",
"message": "This API key is restricted to submitting opt-ins and cannot access this resource."
}{
"status": "error",
"message": "Resource not found"
}{
"status": "error",
"message": "Rate limit exceeded for this organization. Retry after the number of seconds in the Retry-After header."
}