Developer Handover Pack
Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.
Developer Handover Pack
Lead Capture & Routing
[YourCompany.com] · Sales Department · Prepared by FullSpec · [Today's Date]
This document gives the FullSpec build team everything needed to configure, wire, and ship the Lead Capture and Routing automation. It covers the current-state process map with bottlenecks identified, full agent specifications with IO contracts, an end-to-end data flow trace, and the recommended build stack. All build decisions and integrations are handled by FullSpec. The process owner's responsibilities are limited to supplying the scoring rubric, confirming territory rules, and approving go-live after the test pass. Reach the FullSpec team at support@gofullspec.com for any pre-build queries.
01Process snapshot
02Agent specifications
This agent is the first node in the automated flow. It fires the instant a new lead submission is detected from any connected source, including Typeform webhook, Gmail inbound alias polling, or a direct ad-platform webhook. It checks HubSpot for an existing contact by exact email match and normalised phone match, merges or updates if a duplicate is found, and creates a new contact record if none exists. It then applies the configured numeric scoring rubric against firmographic fields (company size, industry, geography) and form-answer fields (service interest, budget indicator, urgency signal) to produce a total score. Based on configurable score thresholds, it tags the lead as Hot, Warm, or Low Priority and emits a structured enriched-lead payload for the routing agent. Low Priority leads are flagged for a human review step before the routing agent acts on them. All other leads pass straight through.
// Input typeform_response_id: string submission_timestamp: ISO8601 lead_source: enum [typeform, gmail_alias, webhook] first_name: string last_name: string email: string phone: string | null company_name: string | null company_size: string | null service_interest: string budget_indicator: string | null urgency_signal: string | null raw_form_answers: object // Output hubspot_contact_id: string is_duplicate: boolean duplicate_action: enum [merged, updated, created] score_total: integer (0-100) score_tier: enum [Hot, Warm, Low Priority] enriched_company_size: string | null enriched_industry: string | null lifecycle_stage: enum [Lead, MarketingQualifiedLead] requires_human_review: boolean sheets_row_appended: boolean enriched_payload: object
- HubSpot tier requirement: the Contacts API v3 search endpoint (POST /crm/v3/objects/contacts/search) is available on HubSpot Starter and above. Confirm the connected account tier before build begins. If the account is on the free tier, search and merge endpoints are unavailable and the build cannot proceed without an upgrade.
- Deduplication logic: match first by email (exact, lowercased), then by normalised phone (E.164 format, strip spaces and dashes). If both email and phone return different existing contacts, flag as a conflict and route to human review rather than auto-merging.
- Scoring rubric: the numeric model cannot be built until the sales manager provides a documented rubric mapping field values to score weights. This is the single hardest pre-build dependency. Build must not start on this agent until the rubric document is signed off.
- Score threshold configuration: Hot/Warm/Low Priority thresholds must be stored as environment variables in the orchestration platform, not hardcoded, so they can be adjusted post-launch without a rebuild.
- Typeform webhook security: validate the Typeform-Signature header on every inbound POST using the shared HMAC secret from the Typeform account. Reject and log any request that fails signature verification.
- Gmail alias polling: if the Gmail inbound alias trigger is used, the polling interval must not exceed 5 minutes. Use a processed-message ID store (keyed by Gmail message ID) to prevent duplicate processing of the same email across polling cycles.
- Google Sheets append: the Sheets API v4 append call writes to the first empty row after the last populated row in the configured sheet. Confirm the sheet tab name and column order match the manager's existing reporting view before wiring.
- Fallback on enrichment failure: if a third-party enrichment call (if added later) times out, the agent must still proceed with the data from the form submission alone. Do not block the lead pipeline on enrichment.
This agent receives the enriched-lead payload from the Lead Scoring and Enrichment Agent (or from the human review exception step for Low Priority leads that have been approved). It reads the current territory configuration and rep workload data from HubSpot, applies the defined routing rules to select the best available rep, writes the rep assignment and lifecycle stage update to the HubSpot contact record, creates a prioritised follow-up task with a due date matched to the lead's score tier, posts a formatted Slack message to the assigned rep's direct channel or a designated channel, sends a personalised acknowledgement email to the prospect via Gmail within the two-minute SLA, and finally appends a completion status to the Google Sheets log row written by the first agent.
// Input enriched_payload: object (from Agent 1 output) hubspot_contact_id: string score_total: integer score_tier: enum [Hot, Warm, Low Priority] lifecycle_stage: string requires_human_review: boolean human_review_approved: boolean | null territory_config: object (loaded from environment or HubSpot property) // Output assigned_rep_hubspot_owner_id: string assigned_rep_name: string assigned_rep_slack_user_id: string hubspot_contact_updated: boolean follow_up_task_id: string follow_up_task_due_date: ISO8601 slack_message_ts: string slack_channel_id: string gmail_message_id: string acknowledgement_sent_at: ISO8601 sheets_status_updated: boolean // On approval (Low Priority exception path only) human_reviewer_id: string review_decision: enum [approved, rejected] review_timestamp: ISO8601 rejection_reason: string | null
- Territory routing rules: rules must be provided as a structured JSON document before build starts, covering rep HubSpot owner IDs, territory or product-line assignments, and workload cap (maximum open leads per rep). The agent reads this config from a versioned environment variable, not from a live sheet, to avoid runtime dependency on Google Sheets availability.
- Workload balancing: the agent calls the HubSpot Owners API and counts open contacts assigned to each eligible rep. If a rep is at their workload cap, the agent routes to the next eligible rep in priority order. If all eligible reps are at cap, the lead is held and a Slack alert is posted to the sales manager's channel.
- Slack message format: the message must include prospect first name, company, score tier, lead source, a one-line summary of key form answers, and a deep link to the HubSpot contact record. Format using Slack Block Kit (section blocks plus a button action). Do not use legacy attachments.
- Slack channel targeting: route to the rep's personal Slack DM by default (looked up via Slack users.lookupByEmail using the rep's work email stored in HubSpot). Confirm all rep email addresses in HubSpot match their Slack workspace emails before build.
- Gmail OAuth2 scope: the Gmail send scope (https://www.googleapis.com/auth/gmail.send) is sufficient. Do not request broader scopes. The connected Gmail account must be the designated shared sales inbox, not a personal account.
- Acknowledgement email template: a plain-text-and-HTML template must be agreed and supplied before build. The template uses first_name and service_interest as the only merge fields initially. Store the template as an environment variable or a versioned file in the credential store.
- Follow-up task due date logic: Hot leads get a task due 1 business day from routing time; Warm leads 2 business days; Low Priority approved leads 5 business days. Business day calculation must account for weekends. Implement with a simple offset function, not an external calendar API.
- HubSpot lifecycle stage write: set lifecycle stage to MarketingQualifiedLead for Warm and Hot leads on contact create. Do not overwrite lifecycle stage if the contact already exists at a later stage (e.g. Opportunity or Customer).
03End-to-end data flow
// ─────────────────────────────────────────────────────────────────
// TRIGGER: New lead detected
// ─────────────────────────────────────────────────────────────────
SOURCE: Typeform webhook POST → /webhook/typeform/lead-capture
OR: Gmail alias poll → INBOX:sales@[YourCompany.com]
OR: Ad platform webhook POST → /webhook/adplatform/lead-event
TRIGGER PAYLOAD (normalised by orchestration layer):
typeform_response_id : string // unique per submission
submission_timestamp : ISO8601
lead_source : enum [typeform, gmail_alias, webhook]
first_name : string
last_name : string
email : string // lowercased before all downstream calls
phone : string | null
company_name : string | null
company_size : string | null
service_interest : string
budget_indicator : string | null
urgency_signal : string | null
raw_form_answers : object
// ─────────────���───────────────────────────────────────────────────
// AGENT 1 HANDOFF: Lead Scoring and Enrichment Agent
// ─────────────────────────────────────────────────────────────────
STEP 1.1 — HubSpot duplicate check
POST /crm/v3/objects/contacts/search
filter: email eq <normalised_email>
if match → is_duplicate: true, action: update or merge
if no match → is_duplicate: false, action: create
STEP 1.2 — Score computation
score_total = sum(
weight_company_size[company_size],
weight_service_interest[service_interest],
weight_budget_indicator[budget_indicator],
weight_urgency_signal[urgency_signal],
weight_lead_source[lead_source]
)
score_tier = Hot if score_total >= HOT_THRESHOLD (env var)
= Warm if score_total >= WARM_THRESHOLD (env var)
= Low Priority otherwise
STEP 1.3 — HubSpot contact write
POST /crm/v3/objects/contacts (create)
OR PATCH /crm/v3/objects/contacts/{id} (update)
fields written:
firstname, lastname, email, phone, company,
hs_lead_source (custom), lead_score (custom),
lead_score_tier (custom), lifecyclestage
STEP 1.4 — Google Sheets append
POST spreadsheets/{SHEET_ID}/values/{RANGE}:append
row: [submission_timestamp, first_name, last_name, email,
company_name, lead_source, score_total, score_tier,
hubspot_contact_id, 'Pending routing']
AGENT 1 OUTPUT PAYLOAD (passed to Agent 2):
hubspot_contact_id : string
is_duplicate : boolean
duplicate_action : enum [merged, updated, created]
score_total : integer
score_tier : enum [Hot, Warm, Low Priority]
lifecycle_stage : string
requires_human_review : boolean // true when score_tier == Low Priority
sheets_row_appended : boolean
enriched_payload : object // full normalised lead record
// ─────────────────────────────────────────────────────────────────
// DECISION: Low Priority branch
// ─────────────────────────────────────────────────────────────────
if requires_human_review == true:
→ post alert to sales manager Slack channel (LOW_PRIORITY_CHANNEL env var)
→ await human_review_approved flag (webhook callback or manual trigger)
→ if rejected: update HubSpot contact tag to 'Disqualified', end flow
→ if approved: continue to Agent 2 with human_review_approved: true
// ─────────────────────────────────────────────────────────────────
// AGENT 2 HANDOFF: Lead Routing and Notification Agent
// ─────────────────────────────────────────────────────────────────
STEP 2.1 — Rep selection
GET /crm/v3/owners // load all active reps
apply territory_config (env var JSON):
match lead geography / service_interest → eligible_reps[]
count open contacts per eligible rep via HubSpot search
select rep with lowest open count below WORKLOAD_CAP (env var)
if all reps at cap → alert sales manager, hold lead
STEP 2.2 — HubSpot contact update (assign + task)
PATCH /crm/v3/objects/contacts/{hubspot_contact_id}
hubspot_owner_id: assigned_rep_hubspot_owner_id
lifecyclestage: MarketingQualifiedLead (Hot or Warm only)
POST /crm/v3/objects/tasks
hs_task_subject: 'Follow up: {first_name} {last_name} ({score_tier})'
hs_task_type: CALL
hs_timestamp: follow_up_task_due_date
hubspot_owner_id: assigned_rep_hubspot_owner_id
associations: [{contactId: hubspot_contact_id}]
STEP 2.3 — Slack notification
POST https://slack.com/api/chat.postMessage
channel: assigned_rep_slack_user_id (DM)
blocks: Block Kit (section: prospect name, score tier, source,
key form answers summary; button: 'Open in HubSpot'
url: https://app.hubspot.com/contacts/{portal_id}/contact/{hubspot_contact_id})
STEP 2.4 — Gmail acknowledgement
POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
to: lead email
from: sales@[YourCompany.com]
subject: 'Thanks for reaching out, {first_name}'
body: personalised template (merge: first_name, service_interest)
sent within 120 seconds of trigger timestamp
STEP 2.5 — Google Sheets status update
PATCH spreadsheets/{SHEET_ID}/values/{ROW_RANGE}
update column 'routing_status' to assigned_rep_name
update column 'acknowledgement_sent' to acknowledgement_sent_at
// ─────────────────────────────────────────────────────────────────
// FINAL OUTPUT
// ─────────────────────────────────────────────────────────────────
assigned_rep_name : string
hubspot_contact_updated : boolean
follow_up_task_id : string
slack_message_ts : string
gmail_message_id : string
acknowledgement_sent_at : ISO8601
sheets_status_updated : boolean
// END OF FLOW04Recommended build stack
More documents for this process
Every document generated for Lead Capture & Routing.