FS-DOC-05Technical
Integration and API Spec
Sales Pipeline Management
[YourCompany.com] · Sales Department · Prepared by FullSpec · [Today's Date]
This document is the authoritative technical reference for every integration point in the Sales Pipeline Management automation. It covers tool authentication, required OAuth scopes, webhook configuration, field mappings between agents, credential store contents, and defined failure behaviours. The FullSpec team uses this specification to build and maintain the orchestration layer; your team is not expected to implement anything here, but the process owner should review the credential store contents and field mappings to confirm they match your live HubSpot configuration before build begins.
01Tool inventory
Tool
Role in automation
Auth method
Min plan required
Used by agents
HubSpot
CRM and pipeline data source; deal triggers, record updates, task creation, close logging
OAuth 2.0 (private app token)
Sales Hub Starter ($45/mo) or above
Agent 1 (Pipeline Admin), Agent 2 (Deal Health Monitor), Agent 3 (Pipeline Reporting)
Gmail
Sends stage-triggered emails on behalf of the assigned sales rep
OAuth 2.0 (Google Workspace)
Google Workspace Business Starter or above
Agent 1 (Pipeline Admin)
Slack
Delivers stall alerts and deal context to reps and managers
OAuth 2.0 (Bot token via Slack app)
Slack Free or above (Pro recommended for message history)
Agent 2 (Deal Health Monitor)
Google Sheets
Receives daily pipeline report data and close-reason log entries
OAuth 2.0 (Google Workspace service account or user token)
Google Workspace Business Starter or above
Agent 3 (Pipeline Reporting)
Calendly
Triggers deal creation in HubSpot when a meeting is booked
OAuth 2.0 (personal access token or webhook subscription)
Calendly Standard or above (webhooks require paid plan)
Agent 1 (Pipeline Admin)
Workflow automation platform
Orchestration layer: hosts all agent workflows, credential store, polling schedules, and retry logic
Internal; each connected tool authenticates via its own OAuth token stored in the credential store
Platform plan supporting webhooks, scheduled triggers, and multi-step workflows
All agents
Before you connect anything: create a sandbox or test environment for every tool (HubSpot sandbox account, a dedicated Gmail test address, a private Slack channel, a test Google Sheet, and a Calendly test event type) and validate each connection end-to-end before any production credentials are entered. Never store production tokens during initial build and QA.
02Per-tool integration detail
HubSpot
Primary data source and action target for deal records, pipeline stages, owner assignment, task creation, and close logging. Used by all three agents. The automation reads and writes to the Deals, Contacts, and Tasks objects via the HubSpot CRM API v3.
Auth method
OAuth 2.0 private app token. Navigate to HubSpot Settings > Integrations > Private Apps, create a new private app, and generate a token. Store the token in the credential store as HUBSPOT_PRIVATE_APP_TOKEN. Do not use a legacy API key.
Required scopes
crm.objects.deals.read, crm.objects.deals.write, crm.objects.contacts.read, crm.objects.owners.read, crm.objects.pipelines.read, crm.objects.tasks.write, timeline.read, sales-email-read
Webhook / trigger setup
Register a CRM webhook subscription via POST /webhooks/v3/{appId}/subscriptions for event types: deal.creation and deal.propertyChange (property: dealstage). The webhook payload is delivered to the orchestration layer's inbound endpoint. Validate the X-HubSpot-Signature-Version: v3 header on every inbound request using HMAC-SHA256 with the client secret stored as HUBSPOT_WEBHOOK_SECRET.
Required configuration
Pipeline ID stored as HUBSPOT_PIPELINE_ID. Stage IDs for each named stage stored as HUBSPOT_STAGE_{STAGE_SLUG} (e.g. HUBSPOT_STAGE_PROPOSAL_SENT). Owner-to-territory mapping table maintained in the credential store as a JSON object HUBSPOT_OWNER_MAP. Required deal properties: dealname, amount, closedate, hubspot_owner_id, dealstage. All stage names must be standardised before build; inconsistent naming causes missed triggers.
Rate limits
HubSpot CRM API v3: 100 requests per 10 seconds per private app token. At ~40 active deals and 15-25 stage changes per week, peak burst is well under the limit. Throttling is not required at current volume, but the orchestration layer must queue writes and respect 429 responses with exponential backoff.
Constraints
Private app tokens do not expire but must be rotated if compromised. Webhook subscriptions require a publicly reachable HTTPS endpoint; use the orchestration platform's provided inbound URL. Deals must belong to the configured pipeline ID; deals in other pipelines are ignored.
// Inbound webhook payload (deal.creation example)
{
"subscriptionType": "deal.creation",
"objectId": 12345678,
"propertyName": "dealstage",
"propertyValue": "appointmentscheduled",
"changeSource": "CRM_UI",
"occurredAt": 1713600000000
}
// Read deal record: GET /crm/v3/objects/deals/{dealId}?properties=dealname,amount,closedate,hubspot_owner_id,dealstage,hs_lastmodifieddate
// Update deal: PATCH /crm/v3/objects/deals/{dealId} { properties: { hubspot_owner_id, dealstage } }
// Create task: POST /crm/v3/objects/tasks { properties: { hs_task_subject, hs_task_status, hs_task_type, hubspot_owner_id }, associations: [{ dealId }] }Gmail
Used by the Pipeline Admin Agent to send stage-triggered emails on behalf of the assigned sales rep. The automation uses the Gmail API with send-on-behalf-of (delegated access), so emails appear to originate from the rep's address, not a generic inbox.
Auth method
OAuth 2.0 with delegated Google Workspace authority. Each rep's account must be added as a delegated sender. Store each rep's refresh token in the credential store as GMAIL_REFRESH_TOKEN_{REP_ID}. A Google Workspace admin must approve the OAuth client and grant domain-wide delegation if service account access is used.
Required scopes
https://www.googleapis.com/auth/gmail.send, https://www.googleapis.com/auth/gmail.readonly (readonly required for thread ID lookups to reply in-thread where applicable)
Webhook / trigger setup
Gmail is an action target only (no inbound trigger). Email sends are initiated by the orchestration layer in response to HubSpot deal stage webhooks. No Gmail push notification subscription is required.
Required configuration
Email templates stored in the credential store as GMAIL_TEMPLATE_{STAGE_SLUG} (e.g. GMAIL_TEMPLATE_PROPOSAL_SENT). Each template must contain Handlebars-style placeholders: {{prospect_first_name}}, {{deal_name}}, {{rep_full_name}}, {{rep_email}}, {{company_name}}. Template IDs must not be hardcoded in workflow steps. The rep-to-deal-owner mapping is resolved via HUBSPOT_OWNER_MAP at runtime.
Rate limits
Gmail API: 250 quota units per user per second; each send call costs 100 units. At 15-25 stage changes per week the send rate is well within limits. Throttling is not required. Monitor for 429 and 403 (quota exceeded) responses and apply a 60-second wait before retry.
Constraints
Send-on-behalf requires the rep's Google account to be connected individually. If a rep's token is revoked or expires, emails for their deals will fail; the orchestration layer must alert via Slack to SLACK_CHANNEL_OPS. Emails must not be sent to addresses flagged as unsubscribed in HubSpot (check contact hs_email_optout property before sending).
// POST https://gmail.googleapis.com/gmail/v1/users/{repEmail}/messages/send
// Authorization: Bearer {GMAIL_REFRESH_TOKEN_{REP_ID}} (exchanged for access token at runtime)
// Request body (RFC 2822 base64url-encoded)
From: {rep_full_name} <{rep_email}>
To: {prospect_email}
Subject: {template_subject_line}
Content-Type: text/html; charset=utf-8
// Merge fields resolved before encoding:
{{prospect_first_name}} -> HubSpot contact: firstname
{{deal_name}} -> HubSpot deal: dealname
{{rep_full_name}} -> HubSpot owner: firstName + lastName
{{rep_email}} -> HubSpot owner: email
{{company_name}} -> HubSpot contact: companySlack
Used by the Deal Health Monitor to deliver stall alerts to the deal owner and their manager. The Slack app posts to a configured channel or sends a direct message, depending on the alert configuration. All message content is dynamically populated from HubSpot deal data.
Auth method
OAuth 2.0 Bot token. Install a custom Slack app in the workspace via api.slack.com, grant the required scopes, and store the bot token as SLACK_BOT_TOKEN in the credential store. Do not use an incoming webhook URL as the sole integration method; the Bot token is required for DM delivery to individual reps.
Required scopes
chat:write, chat:write.public, im:write, users:read, users:read.email, channels:read
Webhook / trigger setup
Slack is an action target only. The orchestration layer calls the Slack Web API chat.postMessage endpoint. No inbound Slack webhook is required. An incoming webhook URL (SLACK_INCOMING_WEBHOOK_URL) is maintained as a fallback for channel posts if the Bot token becomes invalid.
Required configuration
Store alert channel ID as SLACK_CHANNEL_PIPELINE_ALERTS and ops/error channel ID as SLACK_CHANNEL_OPS. Rep Slack user IDs are resolved at runtime via users.lookupByEmail using the rep's HubSpot owner email. The stall threshold is stored as STALL_THRESHOLD_BUSINESS_DAYS (default: 5) and must not be hardcoded.
Rate limits
Slack Web API: 1 message per second per channel (Tier 3: 50+ requests/minute for chat.postMessage). At current volume (40 active deals, daily check), peak alert volume is under 40 messages per day. Throttling is not required. Apply a 1-second delay between batched messages if sending to multiple channels in a single run.
Constraints
Bot must be invited to any private channel it posts to. If a rep's email cannot be resolved to a Slack user ID (users.lookupByEmail returns empty), fall back to posting in SLACK_CHANNEL_PIPELINE_ALERTS with the rep's name in plain text. Log the resolution failure to SLACK_CHANNEL_OPS.
// POST https://slack.com/api/chat.postMessage
// Authorization: Bearer {SLACK_BOT_TOKEN}
// Request body:
{
"channel": "{rep_slack_user_id or SLACK_CHANNEL_PIPELINE_ALERTS}",
"text": "Deal stall alert: {deal_name}",
"blocks": [
{ "type": "section", "text": { "type": "mrkdwn",
"text": "*:warning: Stalled Deal Alert*\n*Deal:* {deal_name}\n*Days inactive:* {days_since_last_activity}\n*Stage:* {dealstage_label}\n*Owner:* {rep_full_name}" } },
{ "type": "actions", "elements": [
{ "type": "button", "text": { "type": "plain_text", "text": "View in HubSpot" },
"url": "https://app.hubspot.com/contacts/{HUBSPOT_PORTAL_ID}/deal/{deal_id}" }
] }
]
}Google Sheets
Used by the Pipeline Reporting Agent to write daily pipeline summary rows and close-reason log entries. The Sheets API is used for targeted cell and row writes; the automation does not read from Sheets. Two sheets exist within a single Google Sheets document: one for the daily pipeline snapshot and one for the close-reason log.
Auth method
OAuth 2.0 via Google Workspace service account. Create a service account in Google Cloud Console, download the JSON key, and store the key content as GOOGLE_SERVICE_ACCOUNT_KEY in the credential store. Share the target Google Sheets document with the service account email address (editor access).
Required scopes
https://www.googleapis.com/auth/spreadsheets
Webhook / trigger setup
Google Sheets is an action target only. No inbound webhook is configured. Writes are triggered by the orchestration layer on a daily schedule (configured as REPORT_RUN_TIME, default 07:00 in the process owner's timezone) and immediately when a deal is marked won or lost in HubSpot.
Required configuration
Store the Sheets document ID as GSHEETS_PIPELINE_DOC_ID. Store the pipeline snapshot sheet name as GSHEETS_PIPELINE_SHEET_NAME (default: 'Pipeline Snapshot') and the close-reason log sheet name as GSHEETS_CLOSELOG_SHEET_NAME (default: 'Close Reason Log'). Column headers must match the field mapping table exactly (see Section 03). Document ID must not be hardcoded in any workflow step.
Sheet structure
Pipeline Snapshot sheet columns (Row 1 headers): run_date, deal_id, deal_name, pipeline_stage, deal_value_usd, days_in_stage, last_activity_date, owner_name, deal_created_date. Close Reason Log sheet columns: close_date, deal_id, deal_name, outcome, close_reason, deal_value_usd, owner_name.
Rate limits
Google Sheets API v4: 300 read requests per minute per project; 60 write requests per minute per user. At 40 active deals per daily run, the batch append operation uses a single batchUpdate request. Rate limiting is not required at current volume. Apply exponential backoff on 429 responses.
Constraints
The service account must have editor access; viewer access will cause write failures. Row append operations must use values:append with insertDataOption: INSERT_ROWS to avoid overwriting existing data. Do not clear and rewrite the sheet on each run; append only.
// POST https://sheets.googleapis.com/v4/spreadsheets/{GSHEETS_PIPELINE_DOC_ID}/values/{GSHEETS_PIPELINE_SHEET_NAME}!A:I:append
// ?valueInputOption=USER_ENTERED&insertDataOption=INSERT_ROWS
// Authorization: Bearer {service_account_access_token}
// Request body:
{
"values": [
[ "{run_date}", "{deal_id}", "{deal_name}", "{dealstage_label}",
"{amount}", "{days_in_stage}", "{hs_lastmodifieddate}",
"{owner_full_name}", "{createdate}" ]
]
}Calendly
Used as a supplementary trigger source for the Pipeline Admin Agent. When a prospect books a meeting via Calendly, a webhook fires and the orchestration layer creates or updates the corresponding HubSpot deal record without waiting for a rep to log it manually.
Auth method
OAuth 2.0 personal access token. Generate a personal access token in Calendly Settings > Integrations > API and Webhooks. Store as CALENDLY_ACCESS_TOKEN in the credential store. For webhook subscriptions, use the Calendly Webhooks API with the same token.
Required scopes
Calendly personal access tokens are not scope-segmented; the token grants access to all resources under the authenticated user. Restrict the Calendly account connected to the automation to the booking pages relevant to sales pipeline events only.
Webhook / trigger setup
Register a webhook subscription via POST https://api.calendly.com/webhook_subscriptions with events: ['invitee.created']. Set the callback URL to the orchestration layer's Calendly inbound endpoint. Validate inbound requests using the Calendly-Webhook-Signature header (HMAC-SHA256 with CALENDLY_WEBHOOK_SIGNING_KEY stored in the credential store).
Required configuration
Store the Calendly organisation URI as CALENDLY_ORG_URI and the relevant event type URIs as CALENDLY_EVENT_TYPE_URI_{SLUG}. Map Calendly event type slugs to HubSpot pipeline stages using the CALENDLY_STAGE_MAP JSON object in the credential store. Do not hardcode event type URIs.
Rate limits
Calendly API: 100 requests per minute. At current volume (15-25 bookings per week at most), rate limiting is not required. Apply a 1-second delay between consecutive Calendly API calls if batching invitee lookups.
Constraints
Webhooks require a Calendly Standard plan or above. The webhook subscription must be recreated if the signing key is rotated. Calendly does not retry failed webhook deliveries; the orchestration layer must acknowledge with HTTP 200 within 5 seconds or the event is lost. If acknowledgement fails, fall back to a daily reconciliation poll of recent invitees.
// Inbound Calendly webhook payload (invitee.created)
{
"event": "invitee.created",
"payload": {
"event_type": { "uri": "{CALENDLY_EVENT_TYPE_URI_SLUG}", "name": "Sales Discovery Call" },
"invitee": {
"name": "Jane Prospect",
"email": "jane@prospect.com",
"scheduled_event": { "start_time": "2024-05-10T14:00:00Z" }
}
}
}
// Resolved output to Pipeline Admin Agent:
prospect_email -> invitee.email
prospect_name -> invitee.name
meeting_datetime -> payload.invitee.scheduled_event.start_time
initial_stage -> CALENDLY_STAGE_MAP[event_type_slug]Integration and API SpecPage 1 of 4
FS-DOC-05Technical
03Field mappings between tools
The tables below define the exact field mappings for each agent handoff. Source field names use the native API property name for the source tool; destination field names use the native API property name for the destination tool. All field names are case-sensitive.
Agent 1 handoff: Calendly to HubSpot (Pipeline Admin Agent, deal creation from booking)
Source tool
Source field
Destination tool
Destination field
Calendly
payload.invitee.email
HubSpot
email
Calendly
payload.invitee.name
HubSpot
dealname
Calendly
payload.invitee.scheduled_event.start_time
HubSpot
closedate (initial estimate: +30 days)
Calendly
CALENDLY_STAGE_MAP[event_type_slug]
HubSpot
dealstage
Calendly
payload.invitee.name
HubSpot
contact.firstname + contact.lastname
Calendly
payload.invitee.email
HubSpot
contact.email
Agent 1 handoff: HubSpot to Gmail (Pipeline Admin Agent, stage-triggered email send)
Source tool
Source field
Destination tool
Destination field
HubSpot
contact.firstname
Gmail
template placeholder: {{prospect_first_name}}
HubSpot
deal.dealname
Gmail
template placeholder: {{deal_name}}
HubSpot
owner.firstName + owner.lastName
Gmail
template placeholder: {{rep_full_name}}
HubSpot
owner.email
Gmail
template placeholder: {{rep_email}} and From: header
HubSpot
contact.company
Gmail
template placeholder: {{company_name}}
HubSpot
deal.dealstage
Gmail
template selection key: GMAIL_TEMPLATE_{STAGE_SLUG}
HubSpot
contact.email
Gmail
To: header
Agent 2 handoff: HubSpot to Slack (Deal Health Monitor, stall alert)
Source tool
Source field
Destination tool
Destination field
HubSpot
deal.dealname
Slack
blocks[0].text: *Deal:* {deal_name}
HubSpot
deal.hs_lastmodifieddate
Slack
blocks[0].text: *Days inactive:* {days_since_last_activity}
HubSpot
deal.dealstage (label resolved from HUBSPOT_STAGE_{SLUG})
Slack
blocks[0].text: *Stage:* {dealstage_label}
HubSpot
owner.firstName + owner.lastName
Slack
blocks[0].text: *Owner:* {rep_full_name}
HubSpot
owner.email
Slack
users.lookupByEmail -> channel (DM to rep)
HubSpot
deal.id + HUBSPOT_PORTAL_ID
Slack
blocks[1].elements[0].url (HubSpot deal deep link)
Agent 3 handoff: HubSpot to Google Sheets (Pipeline Reporting Agent, daily snapshot and close log)
Source tool
Source field
Destination tool
Destination field
HubSpot
deal.id
Google Sheets
Pipeline Snapshot: deal_id (column B)
HubSpot
deal.dealname
Google Sheets
Pipeline Snapshot: deal_name (column C)
HubSpot
deal.dealstage (label)
Google Sheets
Pipeline Snapshot: pipeline_stage (column D)
HubSpot
deal.amount
Google Sheets
Pipeline Snapshot: deal_value_usd (column E)
HubSpot
deal.hs_time_in_{stageid} (seconds / 86400)
Google Sheets
Pipeline Snapshot: days_in_stage (column F)
HubSpot
deal.hs_lastmodifieddate
Google Sheets
Pipeline Snapshot: last_activity_date (column G)
HubSpot
owner.firstName + owner.lastName
Google Sheets
Pipeline Snapshot: owner_name (column H)
HubSpot
deal.createdate
Google Sheets
Pipeline Snapshot: deal_created_date (column I)
HubSpot
deal.closedate
Google Sheets
Close Reason Log: close_date (column A)
HubSpot
deal.hs_deal_stage_probability (won/lost label)
Google Sheets
Close Reason Log: outcome (column D)
HubSpot
deal.closed_lost_reason or deal.hs_closed_won_reason
Google Sheets
Close Reason Log: close_reason (column E)
All field names referencing HubSpot deal stages (dealstage, hs_time_in_{stageid}) must use the internal stage IDs from HUBSPOT_STAGE_{STAGE_SLUG} in the credential store, not display labels. Display labels can change without API notice; internal IDs are stable.
Integration and API SpecPage 2 of 4
FS-DOC-05Technical
04Build stack and orchestration
Orchestration layout
One workflow per agent: Workflow 1 = Pipeline Admin Agent, Workflow 2 = Deal Health Monitor, Workflow 3 = Pipeline Reporting Agent. All three workflows share a single credential store. No credentials are passed between workflows at runtime; each workflow fetches what it needs from the store by key.
Workflow 1 trigger (Pipeline Admin Agent)
Webhook trigger. The orchestration platform exposes a public HTTPS endpoint that receives HubSpot deal.creation and deal.propertyChange webhooks. Signature validation is applied on every inbound request using HMAC-SHA256 with HUBSPOT_WEBHOOK_SECRET before any workflow step executes. A secondary webhook endpoint receives Calendly invitee.created events, validated with CALENDLY_WEBHOOK_SIGNING_KEY.
Workflow 2 trigger (Deal Health Monitor)
Dual trigger: (a) scheduled poll running once every business day at 08:00 in the process owner's local timezone, configured as MONITOR_RUN_TIME; (b) the HubSpot deal.propertyChange webhook (dealstage) also passes through to this workflow so that stall counters can be reset immediately on stage change without waiting for the next scheduled run.
Workflow 3 trigger (Pipeline Reporting Agent)
Dual trigger: (a) scheduled poll running once each business day at REPORT_RUN_TIME (default 07:00 local); (b) HubSpot deal.propertyChange webhook filtered for dealstage values that correspond to closed-won and closed-lost stage IDs (HUBSPOT_STAGE_CLOSED_WON, HUBSPOT_STAGE_CLOSED_LOST), triggering an immediate close-log append.
Webhook signature validation
HubSpot: verify X-HubSpot-Signature-Version: v3 header. Compute HMAC-SHA256 of (HTTP method + URI + request body) using HUBSPOT_WEBHOOK_SECRET. If signature does not match, return HTTP 400 and log to SLACK_CHANNEL_OPS. Calendly: verify Calendly-Webhook-Signature header using HMAC-SHA256 of the raw request body with CALENDLY_WEBHOOK_SIGNING_KEY.
Credential store model
Encrypted key-value store native to the orchestration platform. All secrets are referenced by environment variable key. No secret value is written into a workflow step, code block, or log output. Credential rotation triggers a re-test of the affected workflow in the sandbox environment before production promotion.
Credential store: all keys required before first workflow execution. Replace placeholder values with live credentials in the production store only after sandbox validation is complete.
// CREDENTIAL STORE CONTENTS
// All values are encrypted at rest. Reference by key name only in workflow steps.
// HubSpot
HUBSPOT_PRIVATE_APP_TOKEN = "pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
HUBSPOT_WEBHOOK_SECRET = "<32-char hex secret from HubSpot private app>"
HUBSPOT_PORTAL_ID = "<your HubSpot portal numeric ID>"
HUBSPOT_PIPELINE_ID = "<pipeline UUID from HubSpot pipelines API>"
HUBSPOT_STAGE_APPOINTMENT_SCHED = "<stage internal ID>"
HUBSPOT_STAGE_QUALIFIED = "<stage internal ID>"
HUBSPOT_STAGE_PROPOSAL_SENT = "<stage internal ID>"
HUBSPOT_STAGE_NEGOTIATION = "<stage internal ID>"
HUBSPOT_STAGE_CLOSED_WON = "<stage internal ID>"
HUBSPOT_STAGE_CLOSED_LOST = "<stage internal ID>"
HUBSPOT_OWNER_MAP = '{ "territory_rule_1": "owner_id_1", "territory_rule_2": "owner_id_2" }'
// Gmail (one entry per sales rep)
GMAIL_REFRESH_TOKEN_REP_001 = "<OAuth 2.0 refresh token for rep 1>"
GMAIL_REFRESH_TOKEN_REP_002 = "<OAuth 2.0 refresh token for rep 2>"
GMAIL_OAUTH_CLIENT_ID = "<Google Cloud OAuth client ID>"
GMAIL_OAUTH_CLIENT_SECRET = "<Google Cloud OAuth client secret>"
GMAIL_TEMPLATE_APPOINTMENT_SCHED = "<template string or template store key>"
GMAIL_TEMPLATE_PROPOSAL_SENT = "<template string or template store key>"
GMAIL_TEMPLATE_NEGOTIATION = "<template string or template store key>"
// Slack
SLACK_BOT_TOKEN = "xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"
SLACK_CHANNEL_PIPELINE_ALERTS = "C0XXXXXXXXX"
SLACK_CHANNEL_OPS = "C0YYYYYYYYY"
SLACK_INCOMING_WEBHOOK_URL = "https://hooks.slack.com/services/T00/B00/XXXXXXXX"
// Google Sheets
GOOGLE_SERVICE_ACCOUNT_KEY = '{ "type": "service_account", "project_id": "...", ... }'
GSHEETS_PIPELINE_DOC_ID = "<Google Sheets document ID from URL>"
GSHEETS_PIPELINE_SHEET_NAME = "Pipeline Snapshot"
GSHEETS_CLOSELOG_SHEET_NAME = "Close Reason Log"
// Calendly
CALENDLY_ACCESS_TOKEN = "<Calendly personal access token>"
CALENDLY_WEBHOOK_SIGNING_KEY = "<Calendly webhook signing key>"
CALENDLY_ORG_URI = "https://api.calendly.com/organizations/<uuid>"
CALENDLY_EVENT_TYPE_URI_DISCOVERY = "https://api.calendly.com/event_types/<uuid>"
CALENDLY_STAGE_MAP = '{ "discovery": "HUBSPOT_STAGE_APPOINTMENT_SCHED" }'
// Orchestration config
STALL_THRESHOLD_BUSINESS_DAYS = "5"
MONITOR_RUN_TIME = "08:00"
REPORT_RUN_TIME = "07:00"
PROCESS_TIMEZONE = "America/New_York"Integration and API SpecPage 3 of 4
FS-DOC-05Technical
05Error handling and retry logic
Unhandled exceptions must never fail silently. Every integration point must either succeed, retry with backoff, or post a descriptive failure notice to SLACK_CHANNEL_OPS. A workflow that stops without logging its failure is a critical build defect.
Integration
Scenario
Required behaviour
HubSpot webhook inbound
Signature validation fails (tampered or replayed request)
Return HTTP 400 immediately. Do not execute any workflow step. Log 'Invalid HubSpot webhook signature' with timestamp and source IP to SLACK_CHANNEL_OPS. No retry.
HubSpot API (deal read/write)
429 Too Many Requests
Pause execution for 10 seconds, then retry. Retry up to 3 times with exponential backoff (10s, 30s, 60s). If all retries fail, post 'HubSpot rate limit exceeded, deal {deal_id} not updated' to SLACK_CHANNEL_OPS and halt the run for that deal. Do not drop the event.
HubSpot API (deal read/write)
401 Unauthorized (token expired or revoked)
Do not retry. Post 'HubSpot token invalid, all workflows paused' to SLACK_CHANNEL_OPS immediately. Halt all three agent workflows until the token is replaced in the credential store. A FullSpec team member must be paged via SLACK_CHANNEL_OPS.
HubSpot API (deal read)
Deal record returned with missing required fields (amount, closedate, hubspot_owner_id)
Flag the deal by setting a HubSpot property hs_automation_flag = 'missing_fields'. Post a summary to SLACK_CHANNEL_PIPELINE_ALERTS listing the deal ID and missing fields. Continue processing other deals in the batch. Do not block the workflow.
Gmail (stage email send)
Rep OAuth token expired or refresh fails
Attempt token refresh once using the stored refresh token. If refresh fails, post 'Gmail token invalid for {rep_email}, stage email not sent for deal {deal_id}' to SLACK_CHANNEL_OPS. Log the unsent email payload to the orchestration platform's execution log for manual resend. Do not silently skip.
Gmail (stage email send)
403 Quota exceeded
Wait 60 seconds and retry once. If second attempt fails, log the failure and payload to the execution log and post to SLACK_CHANNEL_OPS. Queue the email for retry on the next workflow execution cycle.
Gmail (stage email send)
Prospect email address flagged as opted out in HubSpot (hs_email_optout = true)
Do not send the email. Log 'Email suppressed: opt-out flag set for contact {contact_id}, deal {deal_id}' to the execution log. Continue the workflow without error. No Slack alert required unless the opt-out flag is unexpected (e.g. newly set within the last 24 hours).
Slack (stall alert)
users.lookupByEmail returns no result for rep email
Fall back to posting the alert in SLACK_CHANNEL_PIPELINE_ALERTS with the rep's full name in plain text instead of a DM. Log 'Slack user not found for {rep_email}' to SLACK_CHANNEL_OPS. Continue processing remaining stall alerts in the batch.
Slack (stall alert)
chat.postMessage returns 429 or 500
Retry after 5 seconds. Retry up to 3 times. If all retries fail, attempt to post via SLACK_INCOMING_WEBHOOK_URL as fallback. If the webhook fallback also fails, log the alert payload to the execution log and post a plain-text email to the process owner's address using Gmail. Do not drop the alert.
Google Sheets (pipeline report write)
403 Insufficient permissions (service account not editor)
Do not retry. Post 'Google Sheets write failed: service account lacks editor access on {GSHEETS_PIPELINE_DOC_ID}' to SLACK_CHANNEL_OPS. Halt the Reporting Agent workflow for that run. The FullSpec team must correct the sharing permissions before the next scheduled run.
Google Sheets (pipeline report write)
429 Quota exceeded (60 writes/min limit hit)
Apply exponential backoff: wait 15 seconds, retry up to 4 times (15s, 30s, 60s, 120s). If all retries fail, cache the failed row payload in the orchestration platform's execution state and prepend it to the next scheduled run's write batch. Post 'Sheets write retries exhausted for run {date}' to SLACK_CHANNEL_OPS.
Calendly webhook inbound
Webhook delivery times out (orchestration platform does not return HTTP 200 within 5 seconds)
Calendly does not retry failed deliveries. The orchestration layer must acknowledge receipt within 5 seconds by returning HTTP 200 before processing begins; processing must run asynchronously. If an event is suspected lost, run a daily reconciliation poll of Calendly scheduled events via GET /scheduled_events?organization={CALENDLY_ORG_URI}&min_start_time={yesterday} and compare against HubSpot deals created in the same window.
For any error scenario that results in a halted workflow or a missed customer-facing action (email not sent, alert not delivered), the FullSpec team will investigate via SLACK_CHANNEL_OPS within one business day. Contact support@gofullspec.com if you observe repeated failures or if SLACK_CHANNEL_OPS is not receiving error notices as expected.
Integration and API SpecPage 4 of 4