Back to Lead Capture & Routing

Integration and API Spec

The reference during the build: every tool connection, auth scope, field mapping, and error-handling rule.

4 pagesPDF · Technical
FS-DOC-05Technical

Integration and API Spec

Lead Capture and Routing

[YourCompany.com] · Sales Department · Prepared by FullSpec · [Today's Date]

This document is the authoritative technical reference for every integration point in the Lead Capture and Routing automation. It covers authentication methods, required OAuth scopes, webhook configuration, field mappings, credential storage, the orchestration layout, and defined failure behaviours for every tool in the stack. The FullSpec team uses this spec to build and validate each connection. It is not intended for the process owner and assumes familiarity with REST APIs, webhook signature validation, and workflow automation tooling.

01Tool inventory

Tool
Role in automation
Auth method
Min plan required
Used by agent(s)
Typeform
Primary lead intake form, webhook source
OAuth 2.0 + webhook secret
Basic ($29/mo)
Agent 1 (Lead Scoring and Enrichment)
HubSpot
CRM, contact records, task creation, rep assignment
Private App token (Bearer)
Starter CRM (free tier sufficient for API; $50/mo for full pipeline)
Agent 1, Agent 2 (both agents)
Gmail
Inbound lead alias listener, prospect acknowledgement emails
OAuth 2.0 (Google)
Google Workspace Business Starter
Agent 1 (trigger), Agent 2 (send)
Slack
Rep notifications and lead alert messages
OAuth 2.0 (Slack Bot Token)
Free tier sufficient; Pro recommended for message history
Agent 2 (Lead Routing and Notification)
Google Sheets
Manager-facing lead volume log, append-only reporting
OAuth 2.0 (Google service account or user OAuth)
Any Google Workspace tier
Agent 1 (scoring config read), Agent 2 (log write)
Orchestration layer (automation platform)
Workflow engine connecting all tools, credential store, retry logic
Per-tool OAuth or API key held in encrypted credential store
N/A — internal build tooling
All agents
Before you connect anything: every integration must be authenticated and smoke-tested against a sandbox or test environment before production credentials are entered. For HubSpot, use a sandbox portal. For Typeform, use a test form with a separate webhook endpoint. For Gmail, use a non-primary alias. For Slack, use a private test channel. For Google Sheets, use a duplicate of the production sheet. Production credentials must never appear in logs, code comments, or version-controlled files.
Integration and API SpecPage 1 of 4
FS-DOC-05Technical

02Per-tool integration detail

Typeform

Typeform acts as the primary lead intake trigger. A webhook is registered against the target form and fires a POST payload to the orchestration layer on every new submission. The orchestration layer validates the webhook signature before processing the payload.

Auth method
OAuth 2.0. Obtain an access token via the Typeform developer portal. Store token as TYPEFORM_OAUTH_TOKEN in credential store. For webhook signature validation, store the form-level secret as TYPEFORM_WEBHOOK_SECRET.
Required scopes
forms:read, responses:read, webhooks:write, webhooks:read
Webhook setup
POST to https://api.typeform.com/forms/{form_id}/webhooks with payload: { tag: 'lead-capture-prod', url: '{orchestration_inbound_url}', enabled: true, secret: '{TYPEFORM_WEBHOOK_SECRET}', verify_ssl: true }. Validate incoming requests by computing HMAC-SHA256 of raw request body using TYPEFORM_WEBHOOK_SECRET and comparing to the Typeform-Signature header value (prefix: sha256=).
Required configuration
Store form_id as TYPEFORM_FORM_ID in credential store. Do not hardcode. The orchestration layer reads this ID at runtime to construct API calls for response retrieval if the webhook payload is incomplete.
Rate limits
Typeform API: 200 requests/minute per token. At 90 leads/month (~3 leads/day) throttling is not required. Implement a 1-second inter-request delay as a precaution for burst scenarios.
Constraints
Webhook payloads do not include all form field metadata on retry events. Always re-fetch the full response via GET /forms/{form_id}/responses/{response_id} if any expected field is null in the webhook body. Webhook delivery is not guaranteed; implement a polling fallback (GET /forms/{form_id}/responses?since={last_processed_ts}) running every 10 minutes.
// Inbound webhook payload (key fields)
form_response.form_id          -> string
form_response.token            -> string (unique response ID)
form_response.submitted_at     -> ISO 8601 timestamp
form_response.answers[].field.ref -> string (maps to scoring field keys)
form_response.answers[].text   -> string | null
form_response.answers[].email  -> string | null
form_response.hidden.utm_source -> string | null

// Output passed to Agent 1
raw_lead.source                = 'typeform'
raw_lead.response_id           = form_response.token
raw_lead.submitted_at          = form_response.submitted_at
raw_lead.fields                = normalised key-value map from answers[]
HubSpot

HubSpot is the system of record for all contact data, lead scores, lifecycle stages, rep assignments, and follow-up tasks. The automation platform communicates with HubSpot via the Contacts, Properties, Owners, and Tasks APIs using a Private App token.

Auth method
HubSpot Private App. Generate token in Settings > Integrations > Private Apps. Store as HUBSPOT_PRIVATE_APP_TOKEN. Pass as Authorization: Bearer {HUBSPOT_PRIVATE_APP_TOKEN} header on all requests. Do not use legacy API keys (deprecated).
Required scopes
crm.objects.contacts.read, crm.objects.contacts.write, crm.objects.owners.read, crm.objects.tasks.read, crm.objects.tasks.write, crm.schemas.contacts.read, crm.schemas.contacts.write
Webhook / trigger
HubSpot is not a trigger source in this automation. All writes are outbound from the orchestration layer to HubSpot REST endpoints. No HubSpot webhook subscription is required.
Required configuration
Store HUBSPOT_PORTAL_ID in credential store. Custom contact properties required: lead_score (number), lead_priority (enumeration: hot, warm, low_priority), lead_source_channel (single-line text), scoring_rubric_version (single-line text). Pipeline stage IDs for 'Lead' and 'Opportunity' must be fetched via GET /crm/v3/pipelines/contacts and stored as HUBSPOT_STAGE_LEAD_ID and HUBSPOT_STAGE_OPP_ID. Rep owner IDs fetched via GET /crm/v3/owners and stored in routing config — never hardcoded.
Rate limits
HubSpot Starter: 100 requests/10 seconds, 40,000 requests/day. At current volume (90 leads/month, ~3 leads/day, approximately 8 API calls per lead) daily usage is approximately 24 calls. No throttling required. Implement exponential backoff on 429 responses (initial delay 1s, max 30s, max 5 retries).
Constraints
Deduplication must use Search API (POST /crm/v3/objects/contacts/search) filtering by email and hs_additional_emails before any create call. If a match is found, PATCH the existing record rather than creating a new contact. The HubSpot Search API has an eventual-consistency lag of up to 60 seconds; implement a direct GET /crm/v3/objects/contacts/{id} lookup by email as a secondary check before declaring no duplicate.
// Deduplication search request body
POST /crm/v3/objects/contacts/search
{ filterGroups: [{ filters: [{ propertyName: 'email', operator: 'EQ', value: raw_lead.email }] }] }

// Contact create/update body (representative fields)
properties.firstname           = raw_lead.first_name
properties.lastname            = raw_lead.last_name
properties.email               = raw_lead.email
properties.phone               = raw_lead.phone
properties.company             = raw_lead.company
properties.lead_score          = scoring_result.numeric_score
properties.lead_priority       = scoring_result.priority_tag
properties.lead_source_channel = raw_lead.source
properties.lifecyclestage      = HUBSPOT_STAGE_LEAD_ID | HUBSPOT_STAGE_OPP_ID
properties.hubspot_owner_id    = routing_result.assigned_owner_id
Gmail

Gmail serves two roles: (1) an inbound alias listener that detects lead emails arriving in the sales inbox as a secondary trigger source, and (2) the outbound sender for personalised prospect acknowledgement emails within two minutes of lead arrival.

Auth method
OAuth 2.0 (Google). Create OAuth credentials in Google Cloud Console under the project bound to the automation. Store access token and refresh token as GMAIL_OAUTH_ACCESS_TOKEN and GMAIL_OAUTH_REFRESH_TOKEN. Use the refresh token flow; access tokens expire after 3600 seconds. Store client ID and secret as GMAIL_CLIENT_ID and GMAIL_CLIENT_SECRET.
Required scopes
https://www.googleapis.com/auth/gmail.readonly (inbound read), https://www.googleapis.com/auth/gmail.send (outbound send), https://www.googleapis.com/auth/gmail.modify (mark processed messages as read / apply label)
Webhook / trigger setup
Use Gmail Push Notifications via Google Pub/Sub. Create a Pub/Sub topic (e.g. lead-inbox-events) and a subscription with push delivery to the orchestration layer inbound URL. Register the watch via POST https://gmail.googleapis.com/gmail/v1/users/me/watch with body: { topicName: 'projects/{gcp_project_id}/topics/lead-inbox-events', labelIds: ['INBOX'], labelFilterBehavior: 'INCLUDE' }. The watch expires every 7 days; implement an automatic renewal scheduled job. Store the Pub/Sub push endpoint secret as GMAIL_PUBSUB_HMAC_SECRET and validate the X-Goog-Signature header on every inbound push notification.
Required configuration
Store target inbox address as GMAIL_LEAD_ALIAS (e.g. leads@[YourCompany.com]). Store the Gmail Draft template ID for the acknowledgement email as GMAIL_ACK_TEMPLATE_ID. Template must use placeholders {{first_name}} and {{rep_name}} resolved at send time. Do not hardcode template body in the workflow; maintain it in a Google Docs-linked draft or a Sheets config row referenced by GMAIL_ACK_TEMPLATE_ID.
Rate limits
Gmail API: 250 quota units/user/second; send message costs 100 units. At 90 leads/month throttling is not required. Pub/Sub push: no practical limit at this volume. Implement a 500ms delay between successive send calls in burst scenarios.
Constraints
Pub/Sub push notifications contain only a message history ID, not the message body. The orchestration layer must call GET /gmail/v1/users/me/messages/{id} to retrieve the full message. Apply a label (e.g. 'lead-processed') after retrieval to prevent reprocessing. Parse the raw MIME body carefully; multipart messages require extraction of the text/plain or text/html part to isolate lead details.
// Inbound Pub/Sub notification (trigger only)
message.data                   -> base64-encoded JSON { emailAddress, historyId }

// After message fetch and parse
raw_lead.source                = 'gmail-alias'
raw_lead.email                 = parsed headers['From']
raw_lead.subject               = parsed headers['Subject']
raw_lead.body_text             = decoded text/plain part

// Outbound acknowledgement send body
POST /gmail/v1/users/me/messages/send
{ raw: base64url(RFC2822 message with To, From, Subject, body) }
Slack

Slack is used exclusively for outbound rep notification messages posted by Agent 2 (Lead Routing and Notification Agent). The bot posts a structured message to a rep-specific DM or a shared team channel with lead context and a HubSpot deep link.

Auth method
OAuth 2.0 (Slack Bot Token). Install the Slack App to the workspace via OAuth flow. Store the Bot User OAuth Token as SLACK_BOT_TOKEN. Pass as Authorization: Bearer {SLACK_BOT_TOKEN} on all API calls.
Required scopes
chat:write, chat:write.public, users:read, users:read.email, im:write, channels:read
Webhook / trigger
Slack is outbound only in this automation. No incoming webhooks or event subscriptions are required.
Required configuration
Store the fallback alert channel ID as SLACK_FALLBACK_CHANNEL_ID (used when a rep's DM cannot be resolved). Map HubSpot owner IDs to Slack user IDs in a Google Sheets config tab (columns: hubspot_owner_id, slack_user_id, rep_name). Store the Sheets tab reference as SLACK_REP_MAP_SHEET_RANGE in the credential store. Do not hardcode individual Slack user IDs in the workflow.
Rate limits
Slack Web API (chat.postMessage): Tier 3, 50+ requests/minute. At current volume this limit is never approached. No throttling needed. Implement a retry with 5-second delay on Slack rate-limit (HTTP 429) responses.
Constraints
Use chat.postMessage with blocks (Block Kit) rather than attachments (deprecated). The message must include: prospect name, lead priority tag, source channel, numeric score, top three form answers, and a direct HubSpot contact URL in the format https://app.hubspot.com/contacts/{HUBSPOT_PORTAL_ID}/contact/{contact_id}. If the rep's Slack user ID cannot be resolved from the mapping sheet, post to SLACK_FALLBACK_CHANNEL_ID with an @-mention placeholder and raise an alert to the process owner.
// chat.postMessage request body (simplified Block Kit)
channel                        = resolved slack_user_id (or SLACK_FALLBACK_CHANNEL_ID)
blocks[0].type                 = 'header' -> text: 'New Lead: {first_name} {last_name}'
blocks[1].type                 = 'section' -> fields:
  Priority: {lead_priority}
  Score: {numeric_score}
  Source: {lead_source_channel}
blocks[2].type                 = 'section' -> text: top 3 form answers (mrkdwn)
blocks[3].type                 = 'actions' -> button: 'Open in HubSpot' url={hubspot_contact_url}
Google Sheets

Google Sheets has two roles: (1) a read source for the scoring rubric configuration consumed by Agent 1, and (2) an append-only write target for the manager-facing lead volume log updated by Agent 2 after each lead is processed.

Auth method
OAuth 2.0 using a Google service account. Create a service account in Google Cloud Console, download the JSON key, and store the entire JSON as GOOGLE_SERVICE_ACCOUNT_JSON in the credential store. Share both Sheets (scoring config and log) with the service account email address at Editor level.
Required scopes
https://www.googleapis.com/auth/spreadsheets (read and write), https://www.googleapis.com/auth/drive.readonly (for file metadata lookups if needed)
Webhook / trigger
Google Sheets does not act as a trigger in this automation. All access is pull (read scoring config) or push (append log row) initiated by the orchestration layer.
Required configuration
Store the scoring rubric Spreadsheet ID as SHEETS_SCORING_CONFIG_ID and the named range as SHEETS_SCORING_RUBRIC_RANGE (e.g. ScoringConfig!A2:F50). Store the lead log Spreadsheet ID as SHEETS_LEAD_LOG_ID and the append range as SHEETS_LOG_APPEND_RANGE (e.g. LeadLog!A:J). Log sheet must have a header row: Timestamp, Response ID, First Name, Last Name, Email, Company, Source, Score, Priority, Assigned Rep. Do not alter the column order after build; the append call maps positionally.
Rate limits
Sheets API v4: 300 read requests/minute/project, 300 write requests/minute/project. At 90 leads/month (approximately 2 reads and 1 write per lead) usage is negligible. No throttling required.
Constraints
Scoring rubric reads must be cached for a minimum of 5 minutes in the orchestration layer to avoid unnecessary API calls on every lead. Invalidate cache when the orchestration layer detects a manual update notification (or on a fixed 5-minute TTL). Append calls must use values.append with valueInputOption: RAW and insertDataOption: INSERT_ROWS to prevent overwriting existing rows.
// Scoring rubric read
GET /v4/spreadsheets/{SHEETS_SCORING_CONFIG_ID}/values/{SHEETS_SCORING_RUBRIC_RANGE}
response.values[]              -> array of [criterion, field_ref, weight, hot_threshold, warm_threshold, low_threshold]

// Log append request body
POST /v4/spreadsheets/{SHEETS_LEAD_LOG_ID}/values/{SHEETS_LOG_APPEND_RANGE}:append
valueInputOption: RAW
insertDataOption: INSERT_ROWS
values: [[timestamp, response_id, first_name, last_name, email, company, source, numeric_score, priority_tag, assigned_rep_name]]
Integration and API SpecPage 2 of 4
FS-DOC-05Technical

03Field mappings between tools

The following tables define the exact field mappings for each agent handoff in the automation flow. Field names are shown in monospace as they appear in each tool's API response or request body. All transformations (format conversions, null coercions) are noted in the Destination field column where applicable.

Handoff 1: Typeform to Agent 1 (Lead Scoring and Enrichment Agent) for HubSpot contact create/update.

Source tool
Source field
Destination tool
Destination field
Typeform
`form_response.answers[ref=first_name].text`
HubSpot
`properties.firstname`
Typeform
`form_response.answers[ref=last_name].text`
HubSpot
`properties.lastname`
Typeform
`form_response.answers[ref=email].email`
HubSpot
`properties.email`
Typeform
`form_response.answers[ref=phone].phone_number`
HubSpot
`properties.phone`
Typeform
`form_response.answers[ref=company_name].text`
HubSpot
`properties.company`
Typeform
`form_response.answers[ref=service_interest].choice.label`
HubSpot
`properties.hs_lead_status` (mapped to closest enumeration value)
Typeform
`form_response.answers[ref=company_size].choice.label`
HubSpot
`properties.numemployees` (parsed to integer range)
Typeform
`form_response.hidden.utm_source`
HubSpot
`properties.hs_analytics_source`
Typeform
`form_response.submitted_at`
HubSpot
`properties.createdate` (ISO 8601 to millisecond epoch)
Typeform
`form_response.token`
HubSpot
`properties.lead_source_channel` (prefixed: `typeform:{token}`)
Scoring engine
`scoring_result.numeric_score`
HubSpot
`properties.lead_score`
Scoring engine
`scoring_result.priority_tag`
HubSpot
`properties.lead_priority` (enum: `hot` | `warm` | `low_priority`)

Handoff 2: Gmail alias inbound parse to Agent 1 for HubSpot contact create/update.

Source tool
Source field
Destination tool
Destination field
Gmail
`headers['From']` (parsed email address)
HubSpot
`properties.email`
Gmail
`headers['From']` (parsed display name, split on space)
HubSpot
`properties.firstname`, `properties.lastname`
Gmail
`headers['Subject']`
HubSpot
`properties.hs_email_subject` (stored for context)
Gmail
`body_text` (regex-extracted phone pattern)
HubSpot
`properties.phone` (null if not found)
Gmail
`body_text` (regex-extracted company pattern)
HubSpot
`properties.company` (null if not found)
Gmail
`internalDate`
HubSpot
`properties.createdate` (millisecond epoch, passed through)
Gmail
Literal string `'gmail-alias'`
HubSpot
`properties.lead_source_channel`

Handoff 3: Agent 1 scored record to Agent 2 (Lead Routing and Notification Agent) for Slack notification.

Source tool
Source field
Destination tool
Destination field
HubSpot
`properties.firstname` + `properties.lastname`
Slack Block Kit
`blocks[0].text.text` (header: prospect full name)
HubSpot
`properties.lead_priority`
Slack Block Kit
`blocks[1].fields[0].text` (Priority label)
HubSpot
`properties.lead_score`
Slack Block Kit
`blocks[1].fields[1].text` (Score value)
HubSpot
`properties.lead_source_channel`
Slack Block Kit
`blocks[1].fields[2].text` (Source label)
HubSpot
`properties.company`
Slack Block Kit
`blocks[1].fields[3].text` (Company)
HubSpot
`id` (HubSpot contact ID)
Slack Block Kit
`blocks[3].elements[0].url` (HubSpot deep link)
HubSpot
`properties.hubspot_owner_id`
Rep mapping sheet
`slack_user_id` lookup key
Rep mapping sheet
`rep_name`
Gmail acknowledgement
`{{rep_name}}` placeholder resolved at send time

Handoff 4: Agent 2 processed record to Google Sheets log append.

Source tool
Source field
Destination tool
Destination field
Orchestration layer
`execution.completed_at` (ISO 8601)
Google Sheets
`LeadLog!A` (Timestamp)
HubSpot / Typeform
`raw_lead.response_id`
Google Sheets
`LeadLog!B` (Response ID)
HubSpot
`properties.firstname`
Google Sheets
`LeadLog!C` (First Name)
HubSpot
`properties.lastname`
Google Sheets
`LeadLog!D` (Last Name)
HubSpot
`properties.email`
Google Sheets
`LeadLog!E` (Email)
HubSpot
`properties.company`
Google Sheets
`LeadLog!F` (Company)
HubSpot
`properties.lead_source_channel`
Google Sheets
`LeadLog!G` (Source)
Scoring engine
`scoring_result.numeric_score`
Google Sheets
`LeadLog!H` (Score)
Scoring engine
`scoring_result.priority_tag`
Google Sheets
`LeadLog!I` (Priority)
Rep mapping sheet
`rep_name`
Google Sheets
`LeadLog!J` (Assigned Rep)
Integration and API SpecPage 3 of 4
FS-DOC-05Technical

04Build stack and orchestration

Orchestration layout
Two discrete workflows, one per agent. Workflow 1 (Lead Scoring and Enrichment Agent) handles all inbound triggers, deduplication, scoring, and HubSpot contact writes. Workflow 2 (Lead Routing and Notification Agent) is triggered by a completion event or output webhook from Workflow 1 and handles rep assignment, HubSpot task creation, Slack notification, Gmail acknowledgement send, and Google Sheets log append.
Credential store
All secrets are stored in the automation platform's encrypted credential store. No credential value appears in workflow logic, code, or version history. Every credential is referenced by its environment variable key name only.
Agent 1 trigger mechanism
Dual-trigger: (1) Webhook from Typeform (event-driven, fires on form submission, signature-validated using HMAC-SHA256 against TYPEFORM_WEBHOOK_SECRET). (2) Pub/Sub push from Gmail (event-driven, fires on new message in GMAIL_LEAD_ALIAS inbox, signature-validated against GMAIL_PUBSUB_HMAC_SECRET). A 10-minute polling fallback on Typeform responses (GET with since parameter) runs independently to catch missed webhook deliveries.
Agent 2 trigger mechanism
Event-driven. Triggered by an outbound webhook or internal queue message emitted by Workflow 1 on successful completion. Payload carries the HubSpot contact ID, scoring result, and raw lead metadata. No polling component. Agent 2 will not fire if Agent 1 has not completed successfully.
Signature validation (Typeform)
On every inbound POST, compute HMAC-SHA256(raw_body, TYPEFORM_WEBHOOK_SECRET). Compare to the Typeform-Signature header value (strip the 'sha256=' prefix before comparison). Reject with HTTP 403 if values do not match; log the rejection with a timestamp and the first 8 characters of the received signature for debugging.
Signature validation (Gmail Pub/Sub)
Validate the X-Goog-Signature header on every Pub/Sub push using GMAIL_PUBSUB_HMAC_SECRET. Reject with HTTP 400 and log the event if validation fails.
Scoring rubric cache
Agent 1 caches the scoring rubric from Google Sheets for 5 minutes (TTL-based). Cache is invalidated at TTL expiry; no manual invalidation endpoint is required at this volume.
Environment
Two environments: staging (sandbox credentials for all tools) and production (live credentials). Workflow 1 and Workflow 2 must each have separate staging and production instances. Staging uses TYPEFORM_FORM_ID_STAGING, HUBSPOT_PORTAL_ID_SANDBOX, GMAIL_LEAD_ALIAS_STAGING, SLACK_FALLBACK_CHANNEL_ID_STAGING, and SHEETS_LEAD_LOG_ID_STAGING.
All keys are referenced by name only in workflow logic. Never interpolate a credential value into a log string or error message.
// Credential store contents (all keys; values stored encrypted)

// Typeform
TYPEFORM_OAUTH_TOKEN            // OAuth 2.0 access token
TYPEFORM_WEBHOOK_SECRET         // HMAC-SHA256 signing secret for webhook validation
TYPEFORM_FORM_ID                // Production form ID
TYPEFORM_FORM_ID_STAGING        // Staging/test form ID

// HubSpot
HUBSPOT_PRIVATE_APP_TOKEN       // Bearer token for all HubSpot API calls
HUBSPOT_PORTAL_ID               // Production portal ID
HUBSPOT_PORTAL_ID_SANDBOX       // Sandbox portal ID
HUBSPOT_STAGE_LEAD_ID           // Pipeline stage ID for 'Lead'
HUBSPOT_STAGE_OPP_ID            // Pipeline stage ID for 'Opportunity'

// Gmail
GMAIL_CLIENT_ID                 // Google OAuth 2.0 client ID
GMAIL_CLIENT_SECRET             // Google OAuth 2.0 client secret
GMAIL_OAUTH_ACCESS_TOKEN        // Short-lived access token (refresh automatically)
GMAIL_OAUTH_REFRESH_TOKEN       // Long-lived refresh token
GMAIL_LEAD_ALIAS                // Production lead inbox address
GMAIL_LEAD_ALIAS_STAGING        // Staging inbox address for testing
GMAIL_ACK_TEMPLATE_ID           // Google Docs Draft or Sheets row ref for ack email template
GMAIL_PUBSUB_HMAC_SECRET        // HMAC secret for Pub/Sub push validation
GMAIL_PUBSUB_TOPIC              // Pub/Sub topic name (full resource path)

// Slack
SLACK_BOT_TOKEN                 // Bot User OAuth Token (xoxb-...)
SLACK_FALLBACK_CHANNEL_ID       // Channel ID for unresolved rep routing alerts
SLACK_FALLBACK_CHANNEL_ID_STAGING // Staging fallback channel
SLACK_REP_MAP_SHEET_RANGE       // Sheets range ref for HubSpot->Slack user ID mapping

// Google Sheets
GOOGLE_SERVICE_ACCOUNT_JSON     // Full service account key JSON (base64-encoded)
SHEETS_SCORING_CONFIG_ID        // Spreadsheet ID for scoring rubric
SHEETS_SCORING_RUBRIC_RANGE     // Named range (e.g. ScoringConfig!A2:F50)
SHEETS_LEAD_LOG_ID              // Spreadsheet ID for lead volume log
SHEETS_LEAD_LOG_ID_STAGING      // Staging log spreadsheet ID
SHEETS_LOG_APPEND_RANGE         // Append range (e.g. LeadLog!A:J)

05Error handling and retry logic

Unhandled exceptions must never fail silently. Every integration point must emit a structured error log entry (timestamp, workflow ID, step name, error code, error message, payload excerpt with PII redacted) and route the failure to the designated alert channel (SLACK_FALLBACK_CHANNEL_ID) when the lead cannot be processed automatically. A failed lead must be held in a dead-letter queue for manual review; it must not be dropped.
Integration
Scenario
Required behaviour
Typeform webhook
Webhook signature validation fails (HMAC mismatch)
Reject with HTTP 403. Log rejection with timestamp and partial signature. Do not process payload. Alert SLACK_FALLBACK_CHANNEL_ID. No retry (invalid signature is not a transient error).
Typeform webhook
Webhook not received (missed delivery)
10-minute polling fallback fetches responses via GET /forms/{id}/responses?since={last_processed_ts}. Any unprocessed response ID not in the processed-IDs log is injected into Workflow 1. Log the recovery event.
Typeform API (response re-fetch)
HTTP 429 rate limit on response retrieval
Exponential backoff: wait 2s, 4s, 8s (max 3 retries). If all retries exhausted, push lead to dead-letter queue and alert SLACK_FALLBACK_CHANNEL_ID with the response ID.
HubSpot Search API (deduplication)
HTTP 429 or 503 on contact search
Exponential backoff: wait 1s, 2s, 4s, 8s, 16s (max 5 retries, cap 30s). If all retries fail, pause workflow 1 execution for 60s then attempt once more. If still failing, push to dead-letter queue and alert. Do not proceed to contact create without a completed deduplication check.
HubSpot Contacts API (create/update)
Duplicate contact created despite search (eventual-consistency race)
On HTTP 409 Conflict response or on detection of a duplicate contact ID in the response, perform a merge via POST /crm/v3/objects/contacts/merge specifying the primary and secondary contact IDs. Log the merge event with both IDs.
HubSpot Contacts API (create/update)
Required field value fails HubSpot validation (e.g. malformed email)
Sanitise and retry once with corrected value. If sanitisation is not possible, set the field to null, flag the property in a HubSpot note on the contact record, and notify the rep in Slack that the field requires manual correction.
Gmail Pub/Sub push (inbound trigger)
Pub/Sub watch expiry (watch expires every 7 days)
A scheduled job runs daily and calls POST /gmail/v1/users/me/watch to renew the watch with a 7-day TTL. If the renewal fails, fall back to polling the GMAIL_LEAD_ALIAS inbox every 5 minutes via GET /gmail/v1/users/me/messages?labelIds=INBOX&q=is:unread. Alert SLACK_FALLBACK_CHANNEL_ID that polling mode is active.
Gmail API (acknowledgement send)
HTTP 403 insufficient permissions or token expiry
Attempt OAuth token refresh using GMAIL_OAUTH_REFRESH_TOKEN. If refresh succeeds, retry the send once. If refresh fails, log the error, skip the acknowledgement send, and post a manual fallback alert to SLACK_FALLBACK_CHANNEL_ID with the prospect email and first name so the rep can send manually.
Slack API (chat.postMessage)
HTTP 429 rate limit or Slack user ID not resolvable from mapping sheet
On 429: retry after the Retry-After header value (default 5s if header absent), max 3 retries. If Slack user ID is not found in the mapping sheet: post the notification to SLACK_FALLBACK_CHANNEL_ID instead, include the unresolved HubSpot owner ID, and flag in the log. Do not silently drop the notification.
Google Sheets (scoring rubric read)
API unavailable or stale cache (TTL expired, fetch fails)
Retry the Sheets read twice with 5-second delay. If both retries fail, use the last successfully cached rubric version (up to 15 minutes stale maximum). If no cached version exists, abort Workflow 1, push to dead-letter queue, and alert SLACK_FALLBACK_CHANNEL_ID. Log the rubric version used on every scoring run.
Google Sheets (log append)
HTTP 5xx or network timeout on append write
Retry up to 3 times with exponential backoff (2s, 4s, 8s). If all retries fail, write the log row to a local dead-letter store within the orchestration platform and schedule a background retry job every 10 minutes until the append succeeds. Do not block the main workflow on a log write failure; rep notification must not be delayed.
Workflow 1 to Workflow 2 handoff
Agent 2 not triggered after Agent 1 completes (internal queue failure)
Workflow 1 emits the handoff event and records the HubSpot contact ID in a handoff-log store. A reconciliation job runs every 15 minutes comparing handoff-log entries to Workflow 2 execution records. Any contact ID in the handoff log with no corresponding Workflow 2 execution within 20 minutes triggers a re-fire of the Workflow 2 trigger event and an alert to SLACK_FALLBACK_CHANNEL_ID.
Dead-letter queue entries must be reviewed by the FullSpec team during the first 30 days post-launch as part of the monitoring commitment. After handover, the process owner is responsible for reviewing the SLACK_FALLBACK_CHANNEL_ID channel daily and escalating to support@gofullspec.com for any recurring failure pattern. Dead-letter items older than 48 hours without manual resolution must trigger a support ticket automatically.
Integration and API SpecPage 4 of 4

More documents for this process

Every document generated for Lead Capture & Routing.

Launch Plan
Operations · Owner
View
ROI and Business Case
Finance · Owner
View
Process Runbook / SOP
Operations · Owner
View
Developer Handover Pack
Technical · Developer
View
Test and QA Plan
Quality · Developer
View