FS-DOC-05Technical
Integration and API Spec
Sales Rep Activity Tracking
[YourCompany.com] · Sales Department · Prepared by FullSpec · [Today's Date]
This document defines the exact integration contracts, API scopes, field mappings, credential store layout, and error-handling rules for the Sales Rep Activity Tracking automation. It is written for the FullSpec build team and covers every tool connection across all three agents: Activity Capture Agent, Follow-Up and Task Agent, and Reporting and Nudge Agent. Use this document as the authoritative reference during build, code review, and QA. Nothing here should be derived from memory or assumed from tooling defaults.
01Tool inventory
Tool
Role in automation
Auth method
Min plan required
Used by agents
Automation platform (orchestration layer)
Workflow orchestration, credential store, scheduling, retry logic
N/A (internal)
N/A
All agents
Gmail
Outbound email signal source; detect emails sent to HubSpot contacts
OAuth 2.0
Google Workspace (any tier)
Agent 1
Google Calendar
Meeting event trigger; detect concluded calendar events with external attendees
OAuth 2.0
Google Workspace (any tier)
Agent 1, Agent 2
Zoom
Call transcript and meeting metadata source
OAuth 2.0 (Server-to-Server app)
Zoom Pro or above (cloud recording required)
Agent 1
HubSpot
CRM: contact/deal matching, activity log writes, task creation, daily activity queries
OAuth 2.0 (private app token)
HubSpot Starter or above
Agent 1, Agent 2, Agent 3
Google Sheets
Live activity tracker; append one row per logged activity each evening
OAuth 2.0
Google Workspace (any tier)
Agent 3
Slack
Daily digest posting to sales channel; stale-contact flag alerts
OAuth 2.0 (Bot token)
Slack Free or above
Agent 3
Before you connect anything: create sandbox or test instances for every tool before touching production credentials. For HubSpot, use a developer sandbox portal. For Zoom, test against a separate Zoom account with cloud recording enabled. For Gmail and Google Calendar, use a dedicated test Google Workspace user. For Slack, use a private test channel. Only promote credentials to the production credential store after each connection passes its isolation test.
02Per-tool integration detail
Gmail
Gmail is polled by the Activity Capture Agent to detect outbound emails sent from a rep's account to an email address that exists as a HubSpot contact. The integration reads sent-mail metadata and body text; it does not write to Gmail.
Auth method
OAuth 2.0. Grant access per rep Gmail account. Store one refresh token per rep in the credential store under the key gmail_oauth_{rep_id}.
Required scopes
https://www.googleapis.com/auth/gmail.readonly | https://www.googleapis.com/auth/gmail.metadata
Trigger setup
Poll the Gmail API GET /gmail/v1/users/me/messages endpoint with query q=in:sent after:{last_poll_timestamp} every 5 minutes per rep. Use the messages.get method with format=full to retrieve headers and body. Record the highest internalDate seen as the watermark to avoid reprocessing.
Required configuration
The rep's Gmail address must match the email property on their HubSpot owner record. Store the rep-to-HubSpot-owner mapping in the credential store (not hardcoded). The polling interval and the sent-label filter must be configurable parameters, not constants in code.
Rate limits
Gmail API quota: 250 quota units per user per second; messages.list costs 5 units; messages.get costs 5 units. At ~120 activities/month per rep across a team of up to 20, peak polling load is well within quota. Throttling is not required at current volume, but the polling loop must honour 429 responses with exponential backoff starting at 2 seconds.
Constraints
The integration reads sent mail only. It must not request write or compose scopes. Emails sent via Gmail aliases that differ from the rep's primary address will not be matched unless the alias is registered in the credential store mapping. Attachments are ignored; only plain-text and HTML body is extracted.
// Input
GET /gmail/v1/users/me/messages?q=in:sent after:{last_poll_ts}
GET /gmail/v1/users/me/messages/{id}?format=full
// Output
{
message_id: string,
from_email: string,
to_email: string[],
subject: string,
body_plain: string,
sent_at_epoch: number
}Google Calendar
Google Calendar triggers the Activity Capture Agent when a calendar event ends and has at least one external attendee (i.e. an email domain different from the rep's workspace domain). It also supplies due-date context to the Follow-Up and Task Agent for next-step extraction.
Auth method
OAuth 2.0. One refresh token per rep, stored as gcal_oauth_{rep_id} in the credential store.
Required scopes
https://www.googleapis.com/auth/calendar.readonly
Trigger setup
Poll the Calendar API GET /calendar/v3/calendars/primary/events endpoint with timeMin set to the last poll timestamp and timeMax set to now. Filter for events where status=confirmed and end.dateTime <= now. Evaluate the attendees[] array for at least one email outside the company domain. Poll every 5 minutes. Use nextSyncToken returned by the API to retrieve only changed events on subsequent calls.
Required configuration
The company email domain must be stored as a credential store variable (company_domain) so the external-attendee filter does not require a code change when the domain changes. Calendar IDs other than 'primary' that reps use for prospect meetings must be listed in the credential store as gcal_extra_calendars_{rep_id}.
Rate limits
Google Calendar API: 1,000,000 queries per day per project; 500 queries per 100 seconds per user. At 5-minute polling for up to 20 reps, peak load is approximately 240 requests/hour, well within limits. Throttling is not required at current volume. Honour 429 with exponential backoff starting at 2 seconds.
Constraints
Events created without any external attendee are ignored. All-day events (date type, not dateTime) are ignored. Recurring events fire on each individual occurrence, not on the series. Calendar event descriptions are included in the payload but not treated as transcript substitutes; they are appended to the note as context only if the Zoom transcript is unavailable.
// Input
GET /calendar/v3/calendars/primary/events
?timeMin={last_poll_iso}&timeMax={now_iso}&syncToken={token}
// Output
{
event_id: string,
summary: string,
start_datetime: string (ISO 8601),
end_datetime: string (ISO 8601),
duration_minutes: number,
attendees: [{ email: string, responseStatus: string }],
description: string | null,
conference_data: { entryPoints: [{ uri: string }] } | null
}Zoom
Zoom supplies cloud recording transcripts and meeting metadata to the Activity Capture Agent. The integration uses a Server-to-Server OAuth app registered in the Zoom Marketplace under the [YourCompany.com] account, which avoids per-user OAuth flows and allows access to all user recordings under the account.
Auth method
Server-to-Server OAuth 2.0. Client credentials flow: POST /oauth/token?grant_type=account_credentials&account_id={ZOOM_ACCOUNT_ID}. Store ZOOM_ACCOUNT_ID, ZOOM_CLIENT_ID, and ZOOM_CLIENT_SECRET in the credential store. Access tokens expire after 1 hour; the orchestration layer must refresh before expiry.
Required scopes
recording:read:admin | user:read:admin | meeting:read:admin
Webhook / trigger setup
Subscribe to the recording.completed Zoom webhook event in the Zoom Marketplace app settings. Set the endpoint URL to the automation platform's inbound webhook receiver. Validate every inbound payload using the Zoom webhook secret token (stored as zoom_webhook_secret in the credential store): compute HMAC-SHA256 of the raw request body using the secret and compare to the x-zm-signature header value. Reject any payload where the signature does not match with HTTP 400.
Required configuration
Cloud recording and auto-transcription must be enabled at the Zoom account level before the webhook fires transcript data. The transcript file type to retrieve is VTT; the download URL is found in recording_files[].download_url where file_type=TRANSCRIPT. The download requires the access token as a Bearer header. Store the transcript retention window (default 30 days) as a config variable in case Zoom account settings differ.
Rate limits
Zoom API: 30 requests/second per account for most endpoints; Dashboard and Report APIs have lower limits (not used here). At ~120 meetings/month across the team the webhook-driven approach avoids polling entirely. Throttling is not required. Retry failed transcript downloads up to 3 times with 10-second intervals before raising an alert.
Constraints
If cloud recording is not enabled, the recording.completed webhook will not fire and the transcript will be unavailable. In this case the Activity Capture Agent falls back to Google Calendar event data for note generation and flags the activity log entry with source=calendar_only. Local recordings are not accessible via the API.
// Inbound webhook payload (recording.completed)
{
event: 'recording.completed',
payload: {
object: {
uuid: string,
host_email: string,
topic: string,
start_time: string (ISO 8601),
duration: number (minutes),
recording_files: [
{ file_type: 'TRANSCRIPT', download_url: string, status: 'completed' }
]
}
}
}
// Parsed transcript output
{
meeting_uuid: string,
host_email: string,
topic: string,
start_time: string,
duration_minutes: number,
transcript_text: string
}Integration and API SpecPage 1 of 4
FS-DOC-05Technical
HubSpot
HubSpot is the central data store for the automation. The Activity Capture Agent writes contact activity logs. The Follow-Up and Task Agent creates tasks. The Reporting and Nudge Agent queries activity records for the daily digest. All HubSpot access uses a single private app token with the scopes listed below.
Auth method
HubSpot Private App token (Bearer token). Store as hubspot_private_app_token in the credential store. Private app tokens do not expire but must be rotated if compromised. No OAuth redirect flow is required.
Required scopes
crm.objects.contacts.read | crm.objects.contacts.write | crm.objects.deals.read | crm.objects.deals.write | crm.objects.owners.read | crm.schemas.contacts.read | crm.schemas.deals.read | timeline | tasks | crm.objects.notes.read | crm.objects.notes.write
Webhook / trigger setup
The Follow-Up and Task Agent is triggered by the Activity Capture Agent completing a note write, not by a HubSpot webhook. The Reporting and Nudge Agent uses a scheduled internal trigger (6 PM daily) and queries the HubSpot CRM API directly. No HubSpot webhook subscription is required for this build.
Required configuration
The following must be stored in the credential store, not hardcoded: hubspot_portal_id, hubspot_activity_type_map (JSON object mapping internal type labels to HubSpot engagement type enum values), hubspot_owner_email_map (JSON object mapping rep email to HubSpot owner ID), hubspot_stale_threshold_days (integer, default 7), hubspot_pipeline_id for the active sales pipeline, hubspot_deal_stage_ids (JSON object mapping stage names to stage IDs). Activity properties written: hs_activity_type, hs_note_body, hs_timestamp, hs_task_subject, hs_task_status, hs_task_priority.
Rate limits
HubSpot API: 100 requests/10 seconds per token (Starter tier); burst up to 150. At ~120 activities/month plus daily queries, peak load is approximately 10-15 requests per trigger event. Throttling is not required at current volume. Implement a request queue with a 100ms minimum interval between calls and honour 429 responses with the Retry-After header value.
Constraints
Contact matching is by email address only. If the attendee or Gmail recipient email does not match any contact's hs_email property, the activity is queued to a manual triage log rather than written to HubSpot. Tasks created via the API must use the POST /crm/v3/objects/tasks endpoint with associations to both the contact and the deal. Notes use POST /crm/v3/objects/notes with the same association pattern.
// Contact lookup
POST /crm/v3/objects/contacts/search
body: { filterGroups: [{ filters: [{ propertyName: 'email', operator: 'EQ', value: attendee_email }] }] }
// Activity (note) write
POST /crm/v3/objects/notes
body: { properties: { hs_note_body, hs_timestamp }, associations: [contact_id, deal_id] }
// Task creation
POST /crm/v3/objects/tasks
body: { properties: { hs_task_subject, hs_task_status:'NOT_STARTED', hs_timestamp, hs_task_priority:'MEDIUM', hubspot_owner_id }, associations: [contact_id, deal_id] }
// Activity query (Reporting Agent)
POST /crm/v3/objects/notes/search
body: { filterGroups: [{ filters: [{ propertyName: 'hs_timestamp', operator: 'GTE', value: start_of_day_epoch_ms }] }] }Google Sheets
Google Sheets receives one appended row per logged activity from the Reporting and Nudge Agent each evening. The sheet acts as a live activity tracker for the sales manager and ops lead, removing the need for manual CRM exports.
Auth method
OAuth 2.0 using a dedicated service account. Store the service account JSON key as gsheets_service_account_json in the credential store. The service account must be granted Editor access to the target spreadsheet directly (not via a shared drive).
Required scopes
https://www.googleapis.com/auth/spreadsheets
Trigger setup
No inbound webhook. The Reporting and Nudge Agent appends rows on a scheduled trigger (6 PM daily) using the Sheets API spreadsheets.values.append method. The append is made to the named range ActivityLog on the tab named 'Daily Activity Log'. If the tab does not exist, the agent must raise a configuration error and halt rather than create an unnamed tab.
Required configuration
Store the following in the credential store: gsheets_spreadsheet_id (the full Google Sheets file ID from the URL), gsheets_tab_name (default 'Daily Activity Log'), gsheets_named_range (default 'ActivityLog'). The sheet must contain a header row: Date | Rep Name | Rep Email | Contact Name | Contact Email | Activity Type | Outcome | Note Summary | Deal Name | Deal Stage | Source. The automation appends data rows only; it never modifies the header row.
Rate limits
Sheets API: 300 read requests/minute per project; 300 write requests/minute per project. One append call per activity per evening run. At ~120 activities/month across the team, the daily batch of appends will not exceed 10-15 rows per run, well within limits. Throttling is not required. Honour 429 with backoff.
Constraints
The service account must not have access to any sheet other than the designated tracker. The spreadsheet ID must be confirmed in the credential store before the first production run. If the append returns a 404 (spreadsheet not found), the agent must alert via Slack to the ops channel and halt; it must not attempt to create a new spreadsheet.
// Append call
POST https://sheets.googleapis.com/v4/spreadsheets/{spreadsheet_id}/values/{range}:append
?valueInputOption=USER_ENTERED&insertDataOption=INSERT_ROWS
body: { values: [[date, rep_name, rep_email, contact_name, contact_email,
activity_type, outcome, note_summary, deal_name, deal_stage, source]] }Slack
Slack receives the formatted daily digest from the Reporting and Nudge Agent. Messages are posted to the configured sales channel each morning (8 AM) using a bot token. Stale-contact flag alerts and operational error alerts are posted to a separate ops channel.
Auth method
OAuth 2.0 Bot token. Install a Slack app to the workspace and grant the scopes below. Store the bot token as slack_bot_token in the credential store. Store the sales channel ID as slack_sales_channel_id and the ops alert channel ID as slack_ops_channel_id. Never store channel names; always use channel IDs to avoid breakage if channels are renamed.
Required scopes
chat:write | chat:write.public | channels:read
Trigger setup
No inbound webhook. The Reporting and Nudge Agent posts messages on a scheduled trigger (8 AM daily for the digest; immediate for error alerts). Use the Slack Web API chat.postMessage method with blocks payload for structured formatting.
Required configuration
Store in the credential store: slack_bot_token, slack_sales_channel_id, slack_ops_channel_id, slack_digest_time (default '08:00' in the workspace's local timezone), slack_stale_threshold_days (mirrors hubspot_stale_threshold_days, default 7). The Slack app must be invited to both channels before the first production run.
Rate limits
Slack Web API: Tier 3 rate limit on chat.postMessage: 1 request/second burst, with a per-method limit of approximately 50 requests/minute. The automation posts at most 2 messages per day (digest plus any stale-contact alert). Throttling is not required. Honour 429 responses with the Retry-After header.
Constraints
The bot must not post to DMs or channels other than those specified in the credential store. If the channel ID is invalid, the agent must log the error to the platform's internal error log and halt; it must not attempt to discover a channel by name. Message blocks must not exceed Slack's 50-block limit per message; if the rep count causes the digest to exceed this, split into multiple messages.
// chat.postMessage payload
POST https://slack.com/api/chat.postMessage
body: {
channel: slack_sales_channel_id,
blocks: [
{ type: 'header', text: { type: 'plain_text', text: 'Sales Activity Digest - {date}' } },
{ type: 'section', fields: [
{ type: 'mrkdwn', text: '*Rep*' },
{ type: 'mrkdwn', text: '*Activities Yesterday*' }
]
},
// one section block per rep
{ type: 'divider' },
{ type: 'section', text: { type: 'mrkdwn', text: ':warning: Stale contacts (>{threshold} days): {list}' } }
]
}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. Use the field names exactly as shown. Source fields are in the format returned by the source tool's API. Destination fields are the exact property names or payload keys expected by the destination tool.
Agent 1 handoff: Gmail or Google Calendar or Zoom to HubSpot (Activity Capture Agent writes activity note)
Source tool
Source field
Destination tool
Destination field
Gmail
`to[0].email`
HubSpot
`hs_email` (contact lookup key)
Gmail
`subject`
HubSpot
`hs_note_body` (prepended as subject line)
Gmail
`body_plain`
HubSpot
`hs_note_body` (truncated to 5000 chars)
Gmail
`sent_at_epoch`
HubSpot
`hs_timestamp` (milliseconds)
Gmail
static: `'EMAIL'`
HubSpot
`hs_activity_type`
Google Calendar
`event_id`
HubSpot
`hs_note_body` (reference prefix)
Google Calendar
`summary`
HubSpot
`hs_note_body` (meeting title)
Google Calendar
`end_datetime`
HubSpot
`hs_timestamp` (ISO to epoch ms)
Google Calendar
`duration_minutes`
HubSpot
`hs_note_body` (appended as 'Duration: N min')
Google Calendar
`attendees[*].email`
HubSpot
`hs_email` (contact lookup keys)
Google Calendar
static: `'MEETING'`
HubSpot
`hs_activity_type`
Zoom
`transcript_text`
HubSpot
`hs_note_body` (AI-summarised, 500 char max)
Zoom
`start_time`
HubSpot
`hs_timestamp` (ISO to epoch ms)
Zoom
`host_email`
HubSpot
`hubspot_owner_id` (via owner email map)
Zoom
`topic`
HubSpot
`hs_note_body` (meeting title)
Zoom
static: `'CALL'`
HubSpot
`hs_activity_type`
Agent 2 handoff: HubSpot activity note to HubSpot task (Follow-Up and Task Agent creates task from note)
Source tool
Source field
Destination tool
Destination field
HubSpot (note)
`hs_note_body` (extracted next-step text)
HubSpot (task)
`hs_task_subject`
HubSpot (note)
`hs_timestamp` + 3 business days (default)
HubSpot (task)
`hs_timestamp` (due date)
HubSpot (note)
extracted ISO date from note body
HubSpot (task)
`hs_timestamp` (overrides default if found)
HubSpot (note)
`associations.contact_id`
HubSpot (task)
`associations[].id` (contact)
HubSpot (note)
`associations.deal_id`
HubSpot (task)
`associations[].id` (deal)
HubSpot (note)
`hubspot_owner_id`
HubSpot (task)
`hubspot_owner_id`
HubSpot (note)
static: `'NOT_STARTED'`
HubSpot (task)
`hs_task_status`
HubSpot (note)
static: `'MEDIUM'`
HubSpot (task)
`hs_task_priority`
Agent 3 handoff: HubSpot activity query to Google Sheets and Slack (Reporting and Nudge Agent builds digest and tracker row)
Source tool
Source field
Destination tool
Destination field
HubSpot (note)
`hs_timestamp`
Google Sheets
`Date` column (formatted YYYY-MM-DD)
HubSpot (owner)
`firstName + lastName`
Google Sheets
`Rep Name` column
HubSpot (owner)
`email`
Google Sheets
`Rep Email` column
HubSpot (contact)
`firstname + lastname`
Google Sheets
`Contact Name` column
HubSpot (contact)
`email`
Google Sheets
`Contact Email` column
HubSpot (note)
`hs_activity_type`
Google Sheets
`Activity Type` column
HubSpot (note)
`hs_note_body` (first 200 chars)
Google Sheets
`Note Summary` column
HubSpot (deal)
`dealname`
Google Sheets
`Deal Name` column
HubSpot (deal)
`dealstage`
Google Sheets
`Deal Stage` column
HubSpot (note)
`hs_note_body` source tag
Google Sheets
`Source` column (zoom/calendar/gmail/calendar_only)
HubSpot (owner)
`firstName + lastName`
Slack
digest block rep name field
HubSpot (note)
count per owner per day
Slack
digest block activity count field
HubSpot (contact)
`lastmodifieddate`
Slack
stale-contact flag list (if > threshold days)
04Build stack and orchestration
Orchestration layout
Three separate workflows, one per agent. Each workflow is independently deployable, versioned, and restartable. Workflows share a single credential store namespace. No workflow calls another directly; Agent 2 (Follow-Up and Task Agent) is triggered by the completion event of Agent 1's HubSpot note write, implemented as an internal event bus message within the orchestration layer, not a HubSpot webhook.
Agent 1 trigger mechanism
Hybrid: (a) Gmail and Google Calendar use polling every 5 minutes per rep using watermark-based deduplication (last processed timestamp stored per rep in the credential store). (b) Zoom uses an inbound webhook (recording.completed event). The webhook endpoint must validate the HMAC-SHA256 signature using zoom_webhook_secret before processing. Replay protection: reject any webhook payload with a timestamp older than 5 minutes relative to server time.
Agent 2 trigger mechanism
Internal event: fired by the orchestration layer immediately after Agent 1 successfully writes a note to HubSpot. The event payload carries contact_id, deal_id, note_id, owner_id, and the raw note body text. No external webhook or poll is involved.
Agent 3 trigger mechanism
Scheduled cron: fires at 18:00 (6 PM) local time daily for the HubSpot query and Google Sheets append. A second cron fires at 08:00 (8 AM) the following morning to post the Slack digest using the data compiled the previous evening and stored temporarily in the platform's internal key-value store (keyed as daily_digest_{YYYY-MM-DD}). Both cron times are configurable in the credential store as cron_reporting_time and cron_digest_time.
Shared credential store
All secrets, IDs, and configurable thresholds are stored in the automation platform's encrypted credential store. No value is hardcoded in any workflow step. See the code block below for the full credential store manifest.
Environment separation
Maintain separate credential store namespaces: env:sandbox and env:production. All initial testing uses env:sandbox. Promotion to env:production requires explicit sign-off. Sandbox HubSpot portal ID must differ from the production portal ID.
Credential store manifest (all keys, env:production namespace)
// Gmail (one entry per rep, substitute {rep_id} for each)
gmail_oauth_{rep_id} // OAuth 2.0 refresh token for rep Gmail account
gmail_rep_email_map // JSON: { rep_id: gmail_address }
// Google Calendar (one entry per rep)
gcal_oauth_{rep_id} // OAuth 2.0 refresh token for rep Calendar account
gcal_extra_calendars_{rep_id} // JSON array of additional calendar IDs (may be [])
company_domain // string: e.g. 'yourcompany.com'
// Zoom
zoom_account_id // Zoom Server-to-Server account ID
zoom_client_id // Zoom app client ID
zoom_client_secret // Zoom app client secret
zoom_webhook_secret // HMAC secret for webhook signature validation
zoom_transcript_retention_days // integer, default 30
// HubSpot
hubspot_private_app_token // HubSpot private app Bearer token
hubspot_portal_id // HubSpot portal/account ID
hubspot_owner_email_map // JSON: { rep_email: hubspot_owner_id }
hubspot_activity_type_map // JSON: { 'EMAIL':'EMAIL','MEETING':'MEETING','CALL':'CALL' }
hubspot_pipeline_id // Active sales pipeline ID
hubspot_deal_stage_ids // JSON: { stage_name: stage_id }
hubspot_stale_threshold_days // integer, default 7
// Google Sheets
gsheets_service_account_json // Full service account key JSON (stringified)
gsheets_spreadsheet_id // Google Sheets file ID
gsheets_tab_name // string, default 'Daily Activity Log'
gsheets_named_range // string, default 'ActivityLog'
// Slack
slack_bot_token // Slack OAuth Bot token (xoxb-...)
slack_sales_channel_id // Slack channel ID for daily digest
slack_ops_channel_id // Slack channel ID for operational alerts
slack_digest_time // string HH:MM, default '08:00'
slack_stale_threshold_days // integer, mirrors hubspot_stale_threshold_days
// Orchestration
cron_reporting_time // string HH:MM, default '18:00'
cron_digest_time // string HH:MM, default '08:00'
env // string: 'sandbox' | 'production'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 catch all non-success responses, log the full error payload (tool name, endpoint, HTTP status, response body, timestamp) to the platform's internal error log, and post an alert to slack_ops_channel_id for any failure that prevents a record from being written to HubSpot or Sheets. Silent failures are treated as build defects.
Integration
Scenario
Required behaviour
Gmail poll
OAuth token expired or refresh fails
Retry refresh once immediately. If refresh fails, halt polling for that rep, log the error, and post an ops alert identifying the affected rep. Do not skip to the next poll cycle silently.
Gmail poll
429 rate limit response
Apply exponential backoff: wait 2s, 4s, 8s (max 3 retries). If all retries fail, log the watermark position and resume from that point on the next scheduled poll. Do not drop messages.
Google Calendar poll
syncToken invalidated (HTTP 410 Gone)
Discard the syncToken, perform a full incremental sync from the last known watermark timestamp, and issue a warning log. This is a normal Zoom Calendar API edge case and must not trigger an ops alert unless it recurs more than 3 times in 24 hours.
Zoom webhook
Signature validation failure
Reject the payload with HTTP 400. Log the raw headers and body hash. Do not process. Post an ops alert if more than 3 signature failures occur within 10 minutes (possible replay attack).
Zoom transcript download
Download URL returns 403 or 404
Retry up to 3 times with 10-second intervals (transcript processing may lag the webhook by up to 30 seconds). If all retries fail, fall back to calendar-data-only note generation, tag the HubSpot note with source=calendar_only, and log the Zoom meeting UUID for manual review.
HubSpot contact lookup
No matching contact found (email not in CRM)
Do not write an activity log. Append the unmatched record to a triage queue stored in the platform's internal key-value store (key: triage_queue_{date}). Post a daily summary of unmatched records to slack_ops_channel_id. The rep must create the contact manually before the activity can be logged.
HubSpot note write
429 rate limit (Retry-After header present)
Pause all HubSpot requests for the duration specified in the Retry-After header. Queue pending writes. Resume in order after the pause. Log the delay. Do not discard queued writes.
HubSpot note write
500 or 503 server error
Retry up to 3 times with exponential backoff (5s, 15s, 45s). If all retries fail, store the failed payload in the platform's dead-letter queue (key: dlq_hubspot_{timestamp}) and post an immediate ops alert. A FullSpec team member must manually review and replay the dead-letter queue.
HubSpot task creation
Missing or invalid owner ID
If the hubspot_owner_email_map does not contain the rep's email, create the task unassigned and set hs_task_subject to include the prefix '[UNASSIGNED - owner lookup failed]'. Post an ops alert. Do not skip task creation entirely.
Google Sheets append
Spreadsheet not found (404)
Halt the append immediately. Do not attempt to create a new spreadsheet. Post an ops alert to slack_ops_channel_id with the configured gsheets_spreadsheet_id value for verification. Store the failed row payload in the platform's dead-letter queue.
Google Sheets append
Service account authentication failure
Log the full error. Post an ops alert. Halt all Sheets operations for the current run. Retry on the next scheduled run. If authentication fails on two consecutive runs, escalate to a FullSpec team member for credential rotation.
Slack chat.postMessage
Invalid channel ID (channel_not_found)
Log the error. Do not attempt to discover a channel by name. Fall back to logging the full digest payload to the platform's internal log so the data is not lost. Post to the ops channel if that channel ID is still valid; otherwise escalate to the FullSpec team via support@gofullspec.com.
Dead-letter queue entries must be reviewed at least once per business day during the first 30 days of production operation. The FullSpec team monitors the ops alert channel as part of the standard post-launch support period. Contact support@gofullspec.com to report any error pattern that recurs more than once in a 24-hour window.
Integration and API SpecPage 4 of 4