Code Examples
Complete, production-ready examples for submitting leads from cURL, JavaScript, Python, PHP, and Go.
Basic Submission
A full submission example with traffic data and Q&A answers:
curl -X POST https://api.hermon.io/api/opt-in/submit \
-H "Content-Type: application/json" \
-H "x-api-key: sk_live_your_key_here" \
-d '{
"email": "john@example.com",
"first_name": "John",
"last_name": "Smith",
"source": "LANDING_PAGE",
"phone_e164": "+14155552671",
"phone_country": "US",
"provider_form_name": "Free Strategy Session",
"external_reference": "sub_abc123",
"traffic": {
"utm_source": "facebook",
"utm_medium": "paid",
"utm_campaign": "q1-coaching",
"landing_page": "https://yoursite.com/apply"
},
"questions_answers": [
{
"question": "What is your biggest challenge?",
"answer": "Scaling my business",
"position": 0
}
]
}'Replace sk_live_your_key_here with your actual API key, stored in an environment variable — never hardcoded in source files.
With Idempotency
Use external_reference to prevent duplicate leads when your webhook or form might fire multiple times:
const response = await fetch("https://api.hermon.io/api/opt-in/submit", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.HERMON_API_KEY,
},
body: JSON.stringify({
email: "john@example.com",
first_name: "John",
source: "TYPEFORM",
// Use your form's submission ID as the external reference
external_reference: "typeform-abc123xyz",
}),
});Setter Attribution
Automatically attribute a setter by passing their Clerk user ID in traffic.utm_content:
// Setter attribution via utm_content
// If utm_content starts with "user_" (a Clerk user ID),
// Hermon assigns that user as the setter on the lead.
const response = await fetch("https://api.hermon.io/api/opt-in/submit", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.HERMON_API_KEY,
},
body: JSON.stringify({
email: "john@example.com",
first_name: "John",
source: "LANDING_PAGE",
traffic: {
utm_source: "facebook",
utm_medium: "paid",
utm_campaign: "q1-coaching",
// This assigns user_setterabc123 as the setter
utm_content: "user_setterabc123",
},
}),
});Handling Responses
Always check the response status and handle errors gracefully:
const response = await fetch("https://api.hermon.io/api/opt-in/submit", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.HERMON_API_KEY,
},
body: JSON.stringify(payload),
});
if (response.status === 429) {
// Rate limited — back off and retry
throw new Error("Rate limit exceeded");
}
if (!response.ok) {
const error = await response.json();
console.error("Submission failed:", error.errors);
throw new Error(error.message);
}
const data = await response.json();
const { lead_id, is_new_lead } = data.data;
console.log(is_new_lead ? "New lead created:" : "Existing lead updated:", lead_id);