How to Score Inbound Leads from a Web Form Webhook into Klaviyo
Catch each web-form submission with a Webhook trigger, let an AI agent enrich and score the lead, then write the score onto a Klaviyo profile and route the contact to the right audience list.
What This Integration Does
When a visitor submits a contact or demo-request form on your site, the raw payload tells you very little: a name, an email, maybe a free-text "What are you looking for?" box. This Spojit workflow turns that raw submission into a ranked, actionable lead. It enriches the submission with a quick public lookup, asks an Agent-mode Connector node to produce a numeric lead score and a short rationale, then stamps that score onto the contact's Klaviyo profile and drops them into the audience list that matches their priority. Your sales and marketing flows can then react to the score immediately instead of waiting for a human to triage the inbox.
The run is event-driven. Every form post hits the workflow's Webhook trigger URL, which returns a fast acknowledgement and then runs the rest of the steps in the background. Each submission produces one execution: it reads the body, performs an optional enrichment call, scores the lead, upserts a Klaviyo profile by email (so re-submissions update the same person rather than duplicating them), and adds that profile to a list. Re-runs are safe because Klaviyo matches on email and the lead_score property is simply overwritten with the latest value.
Prerequisites
- A Klaviyo connection added under Connections - Add connection with an API key that can read and write profiles and lists. See the Klaviyo connector reference.
- At least two Klaviyo audience lists created in Klaviyo (for example "Hot Leads" and "Nurture"). Note their list IDs, or look them up at runtime with the
list-liststool. - A signing connection for the Webhook trigger. If your form provider signs requests you can pick its scheme; otherwise use the Spojit or Custom scheme. See setting up a webhook connection.
- Your form must POST JSON. Know which fields carry the email, name, and free-text message so you can map them with
{{ }}variables. - AI credits available on your plan, since the scoring step runs in Agent mode.
Step 1: Receive the form submission with a Webhook trigger
Add a Trigger node and set its type to Webhook. Choose your signing connection so Spojit can verify the request, then copy the generated URL into your form provider's webhook or post-action settings. When a form is submitted, the trigger parses the JSON body and exposes it as {{ input }}, returning 202 with an executionId to the caller. A typical inbound body looks like this:
{
"email": "dana@acme.io",
"first_name": "Dana",
"last_name": "Lee",
"company": "Acme Industrial",
"message": "We have ~200 staff and need to automate PO processing this quarter."
}
Reference fields downstream as {{ input.email }}, {{ input.message }}, and so on. If a field can be missing, plan to handle it in the scoring prompt rather than assuming it is always present.
Step 2: Enrich the lead with an HTTP request
Add a Connector node using the HTTP Requests connector in Direct mode and select the http-get tool. Use it to pull a small piece of public context that improves scoring, for example a company-metadata lookup keyed off the email domain. Set the request URL with a template, such as https://your-enrichment-api.example.com/lookup?domain={{ input.email }}, and add any required header (an API key) in the headers field. Bind the result to a variable like enrichment so the agent can read {{ enrichment.body }}.
Enrichment is optional. If you do not have an enrichment endpoint, skip this step and let the scoring agent work from the form fields alone. Keep the call lightweight: a single http-get keeps the per-lead cost and latency low.
Step 3: Score the lead with an AI agent and structured output
Add a second Connector node and switch it to Agent mode. In Agent mode the agent reads your prompt and decides how to reason over the data; the key feature here is the Response Schema, which forces the agent to return clean JSON instead of prose. Write a prompt that hands the agent the form fields and enrichment, and defines what a good score means:
You are scoring an inbound sales lead from 0 to 100.
Higher means more sales-ready. Weigh company size, buying
intent in the message, and fit for workflow automation.
Lead:
- Name: {{ input.first_name }} {{ input.last_name }}
- Email: {{ input.email }}
- Company: {{ input.company }}
- Message: {{ input.message }}
- Enrichment: {{ enrichment.body }}
Return the score, a tier, and one short reason.
Set the Response Schema so the output is predictable and easy to branch on:
{
"type": "object",
"properties": {
"score": { "type": "integer", "minimum": 0, "maximum": 100 },
"tier": { "type": "string", "enum": ["hot", "warm", "nurture"] },
"reason": { "type": "string" }
},
"required": ["score", "tier", "reason"]
}
Bind the node's result to scoring. Downstream you then have {{ scoring.score }}, {{ scoring.tier }}, and {{ scoring.reason }} available as reliable values.
Step 4: Create or update the Klaviyo profile with the score
Add a Connector node using the Klaviyo connector in Direct mode and select the create-profile tool. Klaviyo matches profiles on email, so creating by email both inserts new contacts and updates existing ones, which keeps re-submissions from duplicating people. In the attributes field, pass standard fields plus a custom properties object that carries the lead score:
{
"email": "{{ input.email }}",
"first_name": "{{ input.first_name }}",
"last_name": "{{ input.last_name }}",
"properties": {
"lead_score": {{ scoring.score }},
"lead_tier": "{{ scoring.tier }}",
"lead_reason": "{{ scoring.reason }}",
"lead_source": "web_form"
}
}
Bind the result to profile so you can read the returned profile ID as {{ profile.data.id }} in the next step. If you already hold a profile ID and want a guaranteed in-place edit, use the update-profile tool instead, passing that id and the same attributes.
Step 5: Route the lead to the right list based on tier
Add a Condition node that branches on {{ scoring.tier }} (or compare {{ scoring.score }} against a threshold such as 70). On the high-priority branch, add a Connector node using the Klaviyo connector in Direct mode with the add-profiles-to-list tool. Set listId to your hot-leads list ID and profileIds to an array containing the new profile:
{
"listId": "YbHotL",
"profileIds": ["{{ profile.data.id }}"]
}
On the other branch, add a second add-profiles-to-list node pointed at your nurture list. If you would rather not hard-code list IDs, add a list-lists Direct-mode call earlier in the workflow and match the right list by name in a Transform node before this step.
Step 6: Notify your team about hot leads
Still on the high-priority branch, add a Send Email node so sales hears about a hot lead the moment it lands. Send Email uses Spojit's built-in mail service, so no connection is required. Set Recipients to your sales address, a templated Subject like Hot lead: {{ input.first_name }} {{ input.last_name }} ({{ scoring.score }}), and a Body that includes {{ scoring.reason }} and the contact's email. Remember that external recipients must be on the org allowlist under Settings - General - Email recipients. For a Slack ping instead, use a Connector node with the Slack connector and the send-message tool.
Tips
- Keep the scoring prompt short and specific. Naming the exact signals you care about (company size, buying intent, fit) gives steadier scores than a vague "rate this lead" prompt.
- Store the rationale. Saving
lead_reasonon the Klaviyo profile lets reps see why a lead scored the way it did without re-running anything. - Use Direct mode for the Klaviyo and HTTP calls and reserve Agent mode for the scoring step only. Direct mode is deterministic and spends no AI credits, so you only pay for the judgement step.
- For higher volume, add an event with the
create-eventtool to record "Lead Scored" in Klaviyo, which lets you trigger Klaviyo flows off the score itself.
Common Pitfalls
- Missing form fields. If
{{ input.message }}or{{ input.company }}can be blank, say so in the prompt so the agent does not penalize an empty field; otherwise scores skew low. - Quoting numbers. Write
"lead_score": {{ scoring.score }}without quotes so it stays a number in Klaviyo. Wrapping it in quotes stores it as text and breaks numeric segments. - Wrong list ID.
add-profiles-to-listneeds the KlaviyolistId, not the list name. Confirm IDs withlist-listsif a profile silently fails to appear in a segment. - Webhook replays. Form providers sometimes retry. Because profiles upsert on email this is harmless for scoring, but enable the trigger's event-id dedup if you want to avoid duplicate hot-lead emails.
Testing
Before pointing live form traffic at the workflow, post a few sample bodies to the Webhook URL with a tool like curl, covering a clearly strong lead and a clearly weak one. Open the run in execution history and inspect each step: confirm {{ input }} parsed correctly, that the agent returned valid JSON matching your Response Schema, and that the Klaviyo profile shows the expected lead_score. Verify the right list received the profile by checking the audience in Klaviyo. Once a handful of test submissions route correctly, switch your production form over and watch the first real runs.