FS-DOC-05Technical
Integration and API Spec
Lead Nurture Sequence
[YourCompany.com] · Sales Department · Prepared by FullSpec · [Today's Date]
This document is the authoritative integration reference for the Lead Nurture Sequence automation. It covers every tool connected in the build, the exact authentication methods and OAuth scopes required, webhook configurations, field mappings between agents, credential store layout, and defined error handling behaviour for every integration point. The FullSpec team uses this specification to configure and validate all connections. No integration detail should be hardcoded outside the credential store described in Section 04.
01Tool inventory
Tool
Role in automation
Auth method
Min plan required
Used by agents
HubSpot
CRM, contact records, lifecycle stage reads and writes, activity logging
OAuth 2.0 (private app token)
Sales Hub Starter ($45/mo or above)
Agent 1, Agent 2, Agent 3
Gmail
Outbound sequence email delivery for all three sequence touchpoints
OAuth 2.0 (Google service account or user token)
Google Workspace (any paid tier)
Agent 2
Calendly
Meeting booking detection via webhook; booking data pulled to trigger alert
OAuth 2.0 (personal access token)
Calendly Standard or above
Agent 2, Agent 3
Slack
Internal sales channel notification on booking or positive reply
OAuth 2.0 (Slack app bot token)
Slack Pro or above (free tier supported at low volume)
Agent 3
Google Sheets
ICP qualification scoring reference matrix; read-only by Agent 1
OAuth 2.0 (service account JSON key)
Google Workspace (any tier)
Agent 1
Automation platform
Workflow orchestration, scheduling, credential store, retry logic, and inter-agent handoffs
Internal (platform credential store)
Vendor-dependent; must support webhook ingestion and scheduled polling
All agents
Before you connect anything: all API credentials must be validated in a sandbox or test environment before production credentials are entered into the credential store. For HubSpot, use a developer test account. For Gmail, send only to internal addresses. For Calendly, use a test event type. For Slack, post to a private test channel. No production leads should be touched during connection testing.
02Per-tool integration detail
HubSpot
Used by Agent 1 (Lead Qualification Agent), Agent 2 (Sequence Messaging Agent), and Agent 3 (Sales Alert Agent). HubSpot is the system of record for all contact data, lifecycle stages, activity logs, and qualification tags. All reads and writes route through the HubSpot v3 CRM API.
Auth method
OAuth 2.0 via HubSpot private app. Generate a private app token in Settings > Integrations > Private Apps. Token is a static bearer token; no refresh cycle required for private apps.
Required scopes
crm.objects.contacts.read, crm.objects.contacts.write, crm.schemas.contacts.read, crm.schemas.contacts.write, timeline.read, timeline.write, crm.objects.deals.read, crm.objects.deals.write, sales-email-read
Webhook / trigger setup
Configure a HubSpot workflow webhook (Settings > Notifications > Webhooks) to fire on contact creation where lifecycle stage equals 'lead'. Alternatively, the orchestration layer polls GET /crm/v3/objects/contacts with filter lifecyclestage=lead and lastmodifieddate > [last_poll_timestamp] on a 5-minute interval. Webhook is preferred; polling is the fallback if webhook delivery fails.
Required configuration
Pipeline stage IDs for 'Lead', 'Contacted', 'Meeting Booked', and 'Long-Term Nurture' must be retrieved via GET /crm/v3/pipelines/contacts and stored in the credential store as named constants, not hardcoded. Custom contact properties 'hs_lead_score', 'qualification_tag', 'sequence_status', and 'last_sequence_touchpoint_date' must exist before go-live.
Rate limits
HubSpot enforces 100 requests per 10 seconds and 250,000 requests per day on Sales Hub Starter. At 90 leads/month (~3 leads/day), peak API usage is estimated at under 50 requests per lead lifecycle. Throttling is not required at current volume; however, exponential backoff must be implemented for 429 responses.
Constraints
Private app tokens do not expire but must be rotated if exposed. The token must never appear in logs or email payloads. All contact writes must use PATCH /crm/v3/objects/contacts/{id} to avoid overwriting unrelated properties.
// Read
GET /crm/v3/objects/contacts/{id}?properties=firstname,lastname,email,lifecyclestage,hs_lead_source,qualification_tag,hs_lead_score
// Write
PATCH /crm/v3/objects/contacts/{id}
body: { properties: { qualification_tag, hs_lead_score, lifecyclestage, sequence_status } }
// Activity log
POST /crm/v3/objects/communications
body: { properties: { hs_communication_channel_type: 'EMAIL', hs_communication_body: '<summary>' } }Gmail
Used exclusively by Agent 2 (Sequence Messaging Agent) for outbound email delivery across all three sequence touchpoints. Email is sent via the Gmail API using pre-approved templates stored in the credential store. No emails are sent without a resolved contact record in HubSpot.
Auth method
OAuth 2.0. Use a dedicated sending Google Workspace account (e.g. outreach@[YourCompany.com]). Generate credentials via Google Cloud Console > APIs and Services > Credentials > OAuth 2.0 Client ID. Store access token and refresh token in credential store. Refresh token does not expire unless revoked; access token expires after 3600 seconds and must be refreshed automatically.
Required scopes
https://www.googleapis.com/auth/gmail.send, https://www.googleapis.com/auth/gmail.readonly, https://www.googleapis.com/auth/gmail.modify
Webhook / trigger setup
Gmail send actions are triggered by the orchestration layer on a schedule: immediately on qualification tag write (touchpoint 1), after 2 business days with no reply (touchpoint 2), after 4 business days with no reply (touchpoint 3). Reply detection uses Gmail API GET /gmail/v1/users/{userId}/threads to check for inbound messages in the same thread. Poll every 15 minutes during business hours (08:00 to 18:00 local time).
Required configuration
Three email template bodies (T1 Welcome, T2 Follow-Up Day 2, T3 Follow-Up Day 4) must be stored in the credential store as named string constants with placeholder tokens: {{firstname}}, {{lead_source}}, {{enquiry_topic}}, {{calendly_link}}. The Calendly booking URL must be stored as a credential constant and injected at send time. The sending address display name must be configured in Google Workspace before go-live.
Rate limits
Gmail API allows 250 quota units per user per second; sending one email costs 100 units. At 90 leads/month with up to 3 emails each, maximum send volume is 270 emails/month, well within the 500/day limit for Google Workspace. Throttling is not required at current volume.
Constraints
Emails must comply with CAN-SPAM and GDPR requirements. Each email must include an unsubscribe link or opt-out instruction. The sending domain must have valid SPF, DKIM, and DMARC records configured before go-live. Do not use a personal Gmail account; only Google Workspace accounts support the required sending volume reliably.
// Send email
POST /gmail/v1/users/{userId}/messages/send
body: { raw: base64url(RFC2822_message) }
// RFC2822 headers required
To: {contact.email}
From: {GMAIL_SENDER_ADDRESS}
Subject: {TEMPLATE_SUBJECT_T1 | T2 | T3}
Content-Type: text/html; charset=UTF-8
// Reply check
GET /gmail/v1/users/{userId}/threads/{threadId}
filter: messages where from != {GMAIL_SENDER_ADDRESS} AND date > {send_timestamp}Calendly
Used by Agent 2 (Sequence Messaging Agent) to detect whether a lead has booked a meeting, and by Agent 3 (Sales Alert Agent) to retrieve booking details for the Slack notification. Calendly webhooks are the primary trigger for the booking path.
Auth method
OAuth 2.0 personal access token. Generated in Calendly App > Integrations > API and Webhooks. Token is a long-lived bearer token; store in credential store under key CALENDLY_API_TOKEN. Organisation URI is required for webhook registration and must be stored as CALENDLY_ORGANISATION_URI.
Required scopes
Calendly personal access tokens are not scope-segmented; the token carries full read access for the authenticated user's events and webhooks. Confirm the token owner is the account hosting the booking event type.
Webhook / trigger setup
Register a webhook subscription via POST /webhook_subscriptions with events: ['invitee.created']. Set the callback URL to the orchestration layer's inbound webhook endpoint. Validate each inbound payload using the Calendly webhook signature header (Calendly-Webhook-Signature); compute HMAC-SHA256 of the raw request body using the signing secret stored as CALENDLY_WEBHOOK_SECRET and compare to the header value before processing any payload.
Required configuration
The event type URI for the sales booking event must be stored as CALENDLY_EVENT_TYPE_URI in the credential store. Only invitee.created events matching this event type URI should trigger the booking path; filter on payload.event_type.uri. The booking link included in sequence emails must match this event type.
Rate limits
Calendly API allows 1,100 requests per hour per token. At 90 leads/month, webhook-driven flow means API polling is minimal. Throttling is not required at current volume.
Constraints
Webhook subscriptions must be re-registered if the orchestration layer endpoint URL changes. Only one active webhook subscription per event type per organisation is supported on Standard plan. Webhook payload contains invitee email, name, event start time, and event URI; these are the fields used for CRM update and Slack alert.
// Inbound webhook payload (invitee.created)
POST {ORCHESTRATION_WEBHOOK_ENDPOINT}
payload.event_type.uri == CALENDLY_EVENT_TYPE_URI
payload.payload.invitee.email -> match to HubSpot contact by email
payload.payload.invitee.name -> Slack alert display name
payload.payload.scheduled_event.start_time -> Slack alert meeting time
// Webhook registration
POST /webhook_subscriptions
body: { url, organization, scope: 'organization', events: ['invitee.created'], signing_key: CALENDLY_WEBHOOK_SECRET }Slack
Used by Agent 3 (Sales Alert Agent) to post a formatted booking or positive-reply notification to the designated sales channel. Slack is write-only in this automation; no data is read from Slack.
Auth method
OAuth 2.0 Slack app bot token. Create a Slack app in the Slack API console, add the bot to the workspace, install it to the sales channel, and store the bot token as SLACK_BOT_TOKEN in the credential store. The channel ID (not name) must be stored as SLACK_SALES_CHANNEL_ID.
Required scopes
chat:write, chat:write.public, channels:read
Webhook / trigger setup
No inbound webhook is used. The orchestration layer calls POST /chat.postMessage when a Calendly invitee.created event is received or when Agent 2 flags a positive reply. Trigger is event-driven; no polling required.
Required configuration
The Slack message block template must be stored in the credential store as SLACK_ALERT_TEMPLATE. The template uses named placeholders: {{lead_name}}, {{meeting_time}}, {{hubspot_contact_url}}. The HubSpot contact record URL is constructed from the contact ID as https://app.hubspot.com/contacts/{HUBSPOT_PORTAL_ID}/contact/{contact_id}. HUBSPOT_PORTAL_ID must be stored in the credential store.
Rate limits
Slack enforces 1 message per second per channel on the chat.postMessage method (Tier 3). At 90 leads/month with at most one alert per booking, peak rate is far below the limit. Throttling is not required at current volume.
Constraints
The bot must be invited to the sales channel before the token will have permission to post. If the channel is private, use chat:write rather than chat:write.public. Do not post lead email addresses or personal data beyond name and meeting time in the Slack message.
// Post booking alert
POST https://slack.com/api/chat.postMessage
Authorization: Bearer {SLACK_BOT_TOKEN}
body: {
channel: {SLACK_SALES_CHANNEL_ID},
blocks: [
{ type: 'section', text: { type: 'mrkdwn',
text: '*New booking:* {{lead_name}} | {{meeting_time}} | <{{hubspot_contact_url}}|View in HubSpot>' }
}
]
}Google Sheets
Used by Agent 1 (Lead Qualification Agent) as a read-only reference for the ICP scoring matrix. The sheet holds the scoring rules that determine whether a lead clears the qualification threshold. The sheet must be structured in a specific format before go-live.
Auth method
OAuth 2.0 via a Google Cloud service account. Download the service account JSON key file and store the entire JSON as the credential value GSHEETS_SERVICE_ACCOUNT_JSON. Share the target spreadsheet with the service account email (read-only). No user interaction is required after initial setup.
Required scopes
https://www.googleapis.com/auth/spreadsheets.readonly, https://www.googleapis.com/auth/drive.readonly
Webhook / trigger setup
No webhook; Agent 1 reads the scoring sheet on each lead qualification run via GET /spreadsheets/{spreadsheetId}/values/{range}. The spreadsheet ID must be stored as GSHEETS_SCORING_SHEET_ID and the named range as GSHEETS_SCORING_RANGE (e.g. 'ICP_Criteria!A2:D50').
Required configuration
The scoring sheet must have four columns in this exact order: criterion_name (string), field_key (HubSpot property name), match_value (expected value or regex), score_weight (integer). A passing threshold score must be stored as GSHEETS_ICP_THRESHOLD in the credential store. Do not hardcode the threshold in the agent logic.
Rate limits
Google Sheets API allows 300 read requests per minute per project. At 90 leads/month, this is approximately 3 reads per day. Throttling is not required at any foreseeable volume for this use case.
Constraints
The service account must not have write access to the sheet. If scoring criteria are updated, the sheet is edited by a human; the agent picks up changes on the next run without redeployment. The sheet must not contain personally identifiable information.
// Read scoring matrix
GET /spreadsheets/{GSHEETS_SCORING_SHEET_ID}/values/{GSHEETS_SCORING_RANGE}
Authorization: Bearer {service_account_access_token}
// Response structure expected
values: [
['criterion_name', 'field_key', 'match_value', 'score_weight'],
['Company size', 'company_size_range', '11-50', '25'],
['Lead source', 'hs_lead_source', 'Organic Search|Referral', '20'],
...
]Integration and API SpecPage 1 of 4
FS-DOC-05Technical
03Field mappings between tools
The tables below define every field handoff between tools at each agent boundary. Use exact property names as shown; any deviation will cause silent mapping failures. All source field names are HubSpot internal property names or API response keys unless otherwise noted.
Agent 1 handoff: HubSpot contact record and Google Sheets scoring matrix to Lead Qualification Agent output written back to HubSpot.
Source tool
Source field
Destination tool
Destination field
HubSpot
`contact.properties.firstname`
Agent 1 (internal)
`lead.first_name`
HubSpot
`contact.properties.lastname`
Agent 1 (internal)
`lead.last_name`
HubSpot
`contact.properties.email`
Agent 1 (internal)
`lead.email`
HubSpot
`contact.properties.hs_lead_source`
Agent 1 (internal)
`lead.source_channel`
HubSpot
`contact.properties.company`
Agent 1 (internal)
`lead.company_name`
HubSpot
`contact.properties.jobtitle`
Agent 1 (internal)
`lead.job_title`
HubSpot
`contact.properties.numemployees`
Agent 1 (internal)
`lead.company_size_range`
Google Sheets
`values[n][0]` (criterion_name)
Agent 1 (internal)
`scoring_rule.criterion`
Google Sheets
`values[n][1]` (field_key)
Agent 1 (internal)
`scoring_rule.hubspot_field`
Google Sheets
`values[n][2]` (match_value)
Agent 1 (internal)
`scoring_rule.expected_value`
Google Sheets
`values[n][3]` (score_weight)
Agent 1 (internal)
`scoring_rule.weight`
Agent 1 (output)
`qualification_result.score`
HubSpot
`contact.properties.hs_lead_score`
Agent 1 (output)
`qualification_result.tag`
HubSpot
`contact.properties.qualification_tag`
Agent 1 (output)
`qualification_result.passed`
HubSpot
`contact.properties.sequence_status` (value: active | flagged_for_review)
Agent 2 handoff: HubSpot contact data to Sequence Messaging Agent for email personalisation, and Agent 2 output written back to HubSpot after each send.
Source tool
Source field
Destination tool
Destination field
HubSpot
`contact.properties.firstname`
Gmail (template)
`{{firstname}}`
HubSpot
`contact.properties.hs_lead_source`
Gmail (template)
`{{lead_source}}`
HubSpot
`contact.properties.subject_of_enquiry`
Gmail (template)
`{{enquiry_topic}}`
HubSpot
`contact.properties.email`
Gmail
`To:` header
Credential store
`CALENDLY_BOOKING_LINK`
Gmail (template)
`{{calendly_link}}`
Gmail API response
`message.id`
HubSpot
`contact.properties.last_email_message_id`
Gmail API response
`thread.id`
HubSpot
`contact.properties.last_email_thread_id`
Agent 2 (send action)
`send_timestamp`
HubSpot
`contact.properties.last_sequence_touchpoint_date`
Agent 2 (send action)
`touchpoint_number` (1|2|3)
HubSpot
`contact.properties.sequence_touchpoint_count`
Agent 2 (send action)
`lifecycle_stage_update`
HubSpot
`contact.properties.lifecyclestage` (value: contacted)
Calendly webhook
`payload.payload.invitee.email`
Agent 2 (lookup)
`hubspot_contact_id` via email match
Calendly webhook
`payload.payload.scheduled_event.start_time`
HubSpot
`contact.properties.next_meeting_date`
Agent 3 handoff: HubSpot contact data and Calendly booking payload to Slack alert message.
Source tool
Source field
Destination tool
Destination field
HubSpot
`contact.properties.firstname` + `contact.properties.lastname`
Slack (template)
`{{lead_name}}`
Calendly webhook
`payload.payload.scheduled_event.start_time`
Slack (template)
`{{meeting_time}}`
HubSpot
`contact.id`
Slack (template)
`{{hubspot_contact_url}}` (constructed)
HubSpot
`contact.properties.lifecyclestage`
HubSpot (write)
`contact.properties.lifecyclestage` (value: opportunity)
The property `subject_of_enquiry` is a custom HubSpot contact property that must be created before go-live if it does not already exist. If this field is blank on a contact record, the email template must fall back to a generic phrase such as 'your recent enquiry' rather than leaving the placeholder unfilled. Implement a fallback default for every template token.
Integration and API SpecPage 2 of 4
FS-DOC-05Technical
04Build stack and orchestration
Orchestration layout
Three discrete workflows, one per agent. Each workflow is self-contained with its own trigger, credential references, and error handler. Workflows share a single credential store and a shared context object (lead_id, contact_email, sequence_status) passed between agents via the orchestration layer's internal data store. No direct tool-to-tool calls outside the orchestration layer.
Agent 1 trigger mechanism
Webhook (preferred): HubSpot contact.creation event fires to the orchestration layer's inbound endpoint. Polling fallback: GET /crm/v3/objects/contacts every 5 minutes, filtering for lifecyclestage=lead and createdate > last_poll_cursor. Cursor stored in the orchestration layer's key-value store and updated after each successful poll.
Agent 2 trigger mechanism
Event-driven: fires immediately when Agent 1 writes qualification_tag=qualified to HubSpot (detected via internal workflow handoff message). Subsequent touchpoints are schedule-triggered: Day 2 and Day 4 jobs fire as cron-style delayed tasks anchored to the first send timestamp. Reply detection runs as a 15-minute polling job during business hours using the stored thread ID.
Agent 3 trigger mechanism
Webhook (Calendly invitee.created): validated using HMAC-SHA256 signature before processing. Positive-reply trigger: Agent 2 emits an internal event when Gmail reply detection returns a non-automated inbound message; this event triggers Agent 3 directly via the orchestration layer's internal message bus.
Webhook signature validation
Calendly: compute HMAC-SHA256 of raw request body using CALENDLY_WEBHOOK_SECRET; compare to value in Calendly-Webhook-Signature header. Reject any request where signatures do not match with HTTP 401. HubSpot webhook (if used): validate via HubSpot client secret hash in X-HubSpot-Signature-v3 header using the same HMAC-SHA256 method with HUBSPOT_WEBHOOK_SECRET.
Credential store type
Platform-native encrypted secret store. All secrets are referenced by key name in workflow configuration. No secret value appears in workflow logic, logs, or error messages. Access is restricted to the service account running the orchestration layer.
Credential store layout: reference by key name only; never inline values in workflow logic or logs
// CREDENTIAL STORE CONTENTS
// All values encrypted at rest; reference by key name only in workflow config
HUBSPOT_PRIVATE_APP_TOKEN = 'pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
HUBSPOT_PORTAL_ID = '12345678'
HUBSPOT_LIFECYCLE_STAGE_LEAD = 'lead'
HUBSPOT_LIFECYCLE_STAGE_CONTACTED = 'contacted'
HUBSPOT_LIFECYCLE_STAGE_OPPORTUNITY = 'opportunity'
HUBSPOT_LIFECYCLE_STAGE_NURTURE = 'long_term_nurture'
HUBSPOT_WEBHOOK_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
GMAIL_CLIENT_ID = 'xxxxxxxx.apps.googleusercontent.com'
GMAIL_CLIENT_SECRET = 'GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxx'
GMAIL_REFRESH_TOKEN = '1//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
GMAIL_SENDER_ADDRESS = 'outreach@[YourCompany.com]'
GMAIL_SENDER_DISPLAY_NAME = '[YourCompany.com] Sales'
GMAIL_TEMPLATE_SUBJECT_T1 = 'Quick note about your enquiry, {{firstname}}'
GMAIL_TEMPLATE_SUBJECT_T2 = 'Following up, {{firstname}}'
GMAIL_TEMPLATE_SUBJECT_T3 = 'Last touch from us, {{firstname}}'
GMAIL_TEMPLATE_BODY_T1 = '<stored HTML string with {{firstname}}, {{lead_source}}, {{enquiry_topic}}, {{calendly_link}}>'
GMAIL_TEMPLATE_BODY_T2 = '<stored HTML string with {{firstname}}, {{calendly_link}}>'
GMAIL_TEMPLATE_BODY_T3 = '<stored HTML string with {{firstname}}, {{calendly_link}}>'
CALENDLY_API_TOKEN = 'eyJxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
CALENDLY_ORGANISATION_URI = 'https://api.calendly.com/organizations/xxxxxxxx'
CALENDLY_EVENT_TYPE_URI = 'https://api.calendly.com/event_types/xxxxxxxx'
CALENDLY_BOOKING_LINK = 'https://calendly.com/[YourCompany.com]/sales-intro'
CALENDLY_WEBHOOK_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
SLACK_BOT_TOKEN = 'xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx'
SLACK_SALES_CHANNEL_ID = 'C0XXXXXXXXX'
SLACK_ALERT_TEMPLATE = '*New booking:* {{lead_name}} | {{meeting_time}} | <{{hubspot_contact_url}}|View in HubSpot>'
GSHEETS_SERVICE_ACCOUNT_JSON = '{ "type": "service_account", "project_id": "...", ... }'
GSHEETS_SCORING_SHEET_ID = '1BxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxA'
GSHEETS_SCORING_RANGE = 'ICP_Criteria!A2:D50'
GSHEETS_ICP_THRESHOLD = '60'
ORCHESTRATION_WEBHOOK_ENDPOINT = 'https://orchestration.[YourCompany.com]/hooks/lead-nurture'
POLL_CURSOR_KEY = 'hubspot_last_poll_timestamp'Integration and API SpecPage 3 of 4
FS-DOC-05Technical
05Error handling and retry logic
Every integration point has a defined failure behaviour. Unhandled exceptions must never fail silently. All errors must be logged with the lead ID, tool name, error code, and timestamp. Where a retry policy is defined, use exponential backoff starting at 30 seconds, doubling on each attempt.
Integration
Scenario
Required behaviour
HubSpot (read)
Contact fetch returns 404 (contact not found)
Log error with contact ID. Halt agent run for this lead. Do not retry. Alert via Slack to SLACK_SALES_CHANNEL_ID with lead ID and 'contact not found' message.
HubSpot (write)
PATCH contact returns 429 (rate limit exceeded)
Retry with exponential backoff: 30s, 60s, 120s (3 attempts). If all retries fail, log error and queue the write for a manual retry job within 15 minutes. Do not skip the write.
HubSpot (write)
PATCH contact returns 5xx (server error)
Retry with exponential backoff: 30s, 60s, 120s. After 3 failures, log error with payload and trigger a Slack alert to the sales channel. Do not continue the sequence until the write is confirmed.
HubSpot (webhook)
Webhook payload fails signature validation
Reject with HTTP 401. Log the source IP and raw payload hash. Do not process the event. No retry; the sending system must resend.
Gmail (send)
Send returns 429 (quota exceeded)
Retry after 60 seconds, then 5 minutes, then 15 minutes (3 attempts). If all fail, log error and flag the contact in HubSpot with sequence_status=send_failed. Trigger manual review Slack alert.
Gmail (send)
Send returns 401 (token expired or revoked)
Attempt token refresh using GMAIL_REFRESH_TOKEN. If refresh succeeds, retry send immediately. If refresh fails, halt all email sends and trigger a critical Slack alert to the sales channel. Requires manual credential rotation.
Gmail (reply detection)
Thread poll returns no results or 503
Retry after 15 minutes (next scheduled poll). Log the miss. If three consecutive polls fail, trigger a Slack alert. The sequence timer continues; a missed reply detection at most delays sequence pause by one poll interval.
Calendly (webhook)
Webhook delivery fails or endpoint returns 5xx
Calendly retries delivery up to 3 times with backoff. Ensure the orchestration endpoint returns HTTP 200 within 10 seconds. If the endpoint is unavailable, log a missed event. Implement a 15-minute polling fallback: GET /scheduled_events filtered by event_type_uri and start_time > last_check for any bookings missed during downtime.
Calendly (webhook)
Booking email does not match any HubSpot contact
Log unmatched booking with invitee email and event time. Post a Slack alert to the sales channel with booking details for manual CRM lookup. Do not discard the booking event.
Slack (post message)
Post returns 404 channel_not_found or 403 missing_scope
Log error. Do not retry automatically. Trigger a fallback: attempt to post to a secondary Slack channel stored as SLACK_FALLBACK_CHANNEL_ID. If that also fails, write the alert content to a HubSpot contact note as a fallback record.
Google Sheets (read)
Sheet read returns 403 (permission denied) or 404
Halt Agent 1 run for the affected lead. Log error with sheet ID. Do not apply a default qualification score. Flag the lead as requires_manual_qualification in HubSpot and trigger a Slack alert. Service account share permissions must be verified.
Orchestration layer (general)
Unhandled exception in any agent workflow
Catch-all error handler must log the full stack trace, lead ID, agent name, and step name. Write sequence_status=error to the HubSpot contact record. Post a Slack alert with the error summary. Never allow an exception to pass silently. All exceptions are visible and actionable.
All Slack error alerts should post to SLACK_SALES_CHANNEL_ID by default. If that channel is unavailable, fall back to SLACK_FALLBACK_CHANNEL_ID. If both are unavailable, write the error summary to a HubSpot internal note on the affected contact record. No error may be discarded without a persistent log entry. Contact support@gofullspec.com if a credential rotation or endpoint change is needed during the build phase.
Integration and API SpecPage 4 of 4