FS-DOC-05Technical
Integration and API Spec
Renewal and Contract Expiry Management
[YourCompany.com] · Sales Department · Prepared by FullSpec · [Today's Date]
This document is the authoritative technical reference for every external service connection used in the Renewal and Contract Expiry Management automation. It covers authentication methods, required OAuth scopes, webhook configuration, field-level data mappings, credential store layout, orchestration design, and failure handling for all five integrated tools plus the orchestration layer. The FullSpec team uses this specification during build and testing. Refer to it for any credential setup, scope decision, or error-handling question that arises during development or future maintenance.
01Tool inventory
Tool
Role in automation
Auth method
Min plan required
Used by agents
HubSpot
CRM source of truth: contract expiry scan, deal enrichment, activity logging, deal stage updates
OAuth 2.0 (private app token)
Sales Hub Starter ($45/mo) or above for API access
Agent 1, Agent 2
Gmail
Sends staged renewal emails from each account manager's connected mailbox; reply detection pauses sequences
OAuth 2.0 (per-user, delegated)
Google Workspace Business Starter or above
Agent 2
DocuSign
Generates renewal contracts from a template and sends for e-signature; webhook returns signed status
OAuth 2.0 (JWT grant / Authorization Code)
DocuSign Business Pro or above (template and API access required)
Agent 3
Slack
Delivers daily renewal summaries to account managers and structured approval requests to sales managers
OAuth 2.0 (Bot Token, Slack App)
Slack Pro or above (for workflow interactivity and slash commands)
Agent 3
Xero
Creates recurring invoices on signed contract; maps line items to renewal product codes
OAuth 2.0 (Authorization Code, PKCE)
Xero Starter or above (accounting API enabled)
Agent 3
Google Sheets
Receives the daily prioritised contract queue written back by the Contract Monitoring Agent for account manager review
OAuth 2.0 (service account or delegated user)
Google Workspace (any tier with Sheets API enabled)
Agent 1
Orchestration layer
Connects all tools; hosts three workflow instances (one per agent); manages credential store, scheduling, retry logic, and branching
Platform-internal (API keys stored in encrypted credential store)
Paid tier supporting webhooks, scheduled triggers, and credential management
All agents
Before you connect anything: set up and validate every integration against sandbox or developer accounts before entering production credentials. HubSpot, DocuSign, Xero, and Google all provide sandbox environments. Confirm each connection returns expected test data before the credential store is populated with live keys.
02Per-tool integration detail
HubSpot
Used by Agent 1 (Contract Monitoring Agent) to scan deal records for expiry dates, enrich contract data, and write the prioritised outreach queue. Used by Agent 2 (Renewal Outreach Agent) to log sent emails and update sequence stage flags on deal records. Used by Agent 3 (Approval and Closing Agent) to move deals to the Renewed stage and write new contract dates post-signature.
Auth method
OAuth 2.0 via HubSpot Private App. Generate a private app token inside the HubSpot developer portal. Store the token in the credential store under key HUBSPOT_PRIVATE_APP_TOKEN. Never hardcode. Token does not expire but must be rotated if permissions change.
Required scopes
crm.objects.deals.read, crm.objects.deals.write, crm.objects.contacts.read, crm.objects.contacts.write, crm.objects.companies.read, crm.schemas.deals.read, crm.schemas.contacts.read, timeline, sales-email-read, tickets
Webhook / trigger setup
Agent 1 uses a scheduled poll (daily, 08:00 local time) via GET /crm/v3/objects/deals with filter on custom property contract_expiry_date within 90 days and deal_stage != 'renewed'. No inbound webhook required from HubSpot for Agent 1. Agent 2 listens for the outreach_queue_updated property change via a platform-internal trigger on the shared queue record. Agent 3 receives a deal ID from Slack approval payload; it does not require a HubSpot webhook.
Required configuration
Custom deal properties required: contract_expiry_date (date), renewal_sequence_stage (enumeration: not_started, day90_sent, day60_sent, day30_sent, paused_reply, approved, sent_to_docusign, renewed), contract_value_usd (number), assigned_account_manager_email (single-line text). Deal pipeline must include a Renewed stage. Store the HubSpot portal ID in the credential store under HUBSPOT_PORTAL_ID.
Rate limits
HubSpot API: 100 requests per 10 seconds (burst), 250,000 requests per day on a paid portal. At 30 to 50 renewals per month the daily scan makes approximately 50 to 100 API calls. No throttling required at current volume. Build in a 200 ms inter-request delay as a precaution.
Constraints
Private app tokens are portal-scoped; each HubSpot account requires its own token. The timeline API requires the timeline scope to create custom timeline events; do not use the deprecated engagements API for new builds.
// Input (Agent 1 scan)
GET /crm/v3/objects/deals?limit=100&properties=contract_expiry_date,dealname,amount,hubspot_owner_id,contract_value_usd,assigned_account_manager_email,renewal_sequence_stage&filterGroups=[{filters:[{propertyName:'contract_expiry_date',operator:'LTE',value:'TODAY+90'},{propertyName:'renewal_sequence_stage',operator:'NEQ',value:'renewed'}]}]
// Output (Agent 2 activity log)
POST /crm/v3/objects/deals/{dealId}/associations/emails
PATCH /crm/v3/objects/deals/{dealId} { properties: { renewal_sequence_stage: 'day90_sent' } }Gmail
Used exclusively by Agent 2 (Renewal Outreach Agent) to send staged personalised renewal emails from each account manager's individual Gmail address, and to monitor for client replies so that the sequence can be paused automatically.
Auth method
OAuth 2.0 per-user delegated access. Each account manager must individually authorise the integration via the Google OAuth consent screen. Tokens (access + refresh) stored in the credential store under keys GMAIL_ACCESS_TOKEN_{USER_ID} and GMAIL_REFRESH_TOKEN_{USER_ID}. Access tokens expire after 3600 seconds; the orchestration layer must refresh automatically using the stored refresh token.
Required scopes
https://www.googleapis.com/auth/gmail.send, https://www.googleapis.com/auth/gmail.readonly, https://www.googleapis.com/auth/gmail.labels, https://www.googleapis.com/auth/gmail.modify
Webhook / trigger setup
Use the Gmail Push Notifications API (Cloud Pub/Sub) to receive real-time notifications of new messages in each account manager's inbox. Subscribe to the INBOX label. The orchestration layer registers a watch on each mailbox via POST https://gmail.googleapis.com/gmail/v1/users/{userId}/watch with topicName pointing to the platform's Pub/Sub endpoint. Watch registrations expire after 7 days and must be renewed automatically. On each Pub/Sub notification, fetch message headers (From, Subject, In-Reply-To, References) and match against open renewal thread IDs to detect replies.
Required configuration
Three email templates must be created and stored in the credential/config store (not hardcoded): RENEWAL_TEMPLATE_90D, RENEWAL_TEMPLATE_60D, RENEWAL_TEMPLATE_30D. Each template uses placeholders: {{client_first_name}}, {{contract_expiry_date}}, {{deal_name}}, {{account_manager_name}}, {{account_manager_email}}. If the team uses shared inboxes or send-as aliases, the send-as alias address must be configured in the Google Workspace admin and verified before use.
Rate limits
Gmail API: 250 quota units per user per second; sending via users.messages.send costs 25 units per call. At 50 renewals per month with three send events each, the maximum daily send volume is approximately 8 emails. Well within limits. No throttling required.
Constraints
The Gmail API cannot send from a mailbox the authenticated user does not own or have delegated access to. Each account manager must individually complete the OAuth flow. Shared inboxes require domain-wide delegation via a Google Workspace service account, which requires Workspace admin approval. If shared inboxes are in use, flag this before build begins.
// Input (reply detection)
Pub/Sub message -> fetch GET /gmail/v1/users/{userId}/messages/{messageId}?format=metadata
Check headers: In-Reply-To matches open renewal threadId
// Output (send renewal email)
POST /gmail/v1/users/{userId}/messages/send
{ raw: base64url(MIME message with template populated, thread headers set) }Integration and API SpecPage 1 of 4
FS-DOC-05Technical
DocuSign
Used by Agent 3 (Approval and Closing Agent) to generate a renewal contract from a pre-built template, send it to the client contact for electronic signature, and receive a signed status webhook that triggers the HubSpot and Xero post-signature updates.
Auth method
OAuth 2.0 JWT Grant (server-to-server, no user interaction after setup) using an RSA key pair registered in the DocuSign developer console. Store the private key as DOCUSIGN_PRIVATE_KEY, the integration key as DOCUSIGN_INTEGRATION_KEY, and the impersonated user GUID as DOCUSIGN_IMPERSONATION_USER_GUID. The JWT assertion is exchanged for an access token via POST https://account.docusign.com/oauth/token. Tokens expire after 3600 seconds; refresh automatically.
Required scopes
signature, impersonation, click.manage, click.send
Webhook / trigger setup
Register a Connect webhook (DocuSign Connect) in the DocuSign admin console targeting the orchestration layer's inbound webhook endpoint. Listen for the envelope event types: envelope-sent, envelope-delivered, envelope-completed, envelope-declined, envelope-voided. Validate inbound payloads using the HMAC-SHA256 signature present in the X-DocuSign-Signature-1 header. The HMAC secret is stored in the credential store as DOCUSIGN_CONNECT_HMAC_SECRET. On envelope-completed, extract the envelopeId, completedDateTime, and signedDocumentUrl and pass them to the HubSpot and Xero update steps.
Required configuration
One DocuSign template must be pre-built and standardised before the automation runs. Store the template ID in the credential store as DOCUSIGN_RENEWAL_TEMPLATE_ID. The template must include the following tab fields (mapped at build time): client_name, client_email, contract_start_date, contract_end_date, annual_value, account_manager_name. The account ID for the DocuSign environment is stored as DOCUSIGN_ACCOUNT_ID. Do not hardcode.
Rate limits
DocuSign API: 1,000 API calls per hour on Business Pro. At 30 to 50 envelopes per month the automation generates fewer than 5 API calls per envelope (create, send, status check, download). Total monthly API usage is under 250 calls. No throttling required.
Constraints
JWT impersonation requires the impersonated user to grant consent once via the DocuSign consent URI before the automation can send on their behalf. DocuSign Connect webhooks do not retry with exponential backoff by default; the orchestration layer must implement retry logic on failed webhook receipt. Non-standard contracts must not use this automated template path; they are routed to a human for manual envelope creation.
// Input (create and send envelope from template)
POST /restapi/v2.1/accounts/{DOCUSIGN_ACCOUNT_ID}/envelopes
{ templateId: DOCUSIGN_RENEWAL_TEMPLATE_ID, templateRoles: [{ email, name, roleName:'Client', tabs:{...} }], status: 'sent' }
// Output (webhook on completion)
POST {orchestration_webhook_endpoint}
Header: X-DocuSign-Signature-1: {hmac_sha256_hex}
Body: { envelopeId, status:'completed', completedDateTime, recipients:[{ email, signedDateTime }] }Slack
Used by Agent 3 (Approval and Closing Agent) to deliver structured daily renewal summaries to account managers and to route approval requests to the sales manager with interactive approve or decline buttons. Approval responses from Slack trigger the DocuSign send step.
Auth method
OAuth 2.0 Bot Token (xoxb-...) issued when the Slack App is installed to the workspace. Store the bot token as SLACK_BOT_TOKEN. Store the signing secret (used to validate inbound interaction payloads) as SLACK_SIGNING_SECRET. Do not hardcode either value.
Required scopes
chat:write, chat:write.public, im:write, channels:read, users:read, users:read.email, incoming-webhook, commands (if slash command fallback is implemented)
Webhook / trigger setup
Enable Interactivity in the Slack App manifest and set the Request URL to the orchestration layer's Slack interaction endpoint. Slack sends a POST to this URL when a user clicks an approve or decline button in a Block Kit message. Validate every inbound payload by computing HMAC-SHA256 over the concatenated string 'v0:' + timestamp + ':' + raw_body using SLACK_SIGNING_SECRET and comparing to the X-Slack-Signature header. Reject any request where the timestamp differs from system time by more than 300 seconds.
Required configuration
Store the sales manager Slack user ID as SLACK_APPROVER_USER_ID and the team notification channel ID as SLACK_RENEWALS_CHANNEL_ID in the credential store. Approval Block Kit templates are defined in the orchestration workflow config, not hardcoded. Each approval message must carry the deal ID and deal name as block metadata so the interaction payload returns them to the orchestration layer without a secondary lookup.
Rate limits
Slack Web API: Tier 3 methods (chat.postMessage) allow 50 requests per minute. At 50 renewals per month the automation sends at most 2 to 3 Slack messages per renewal cycle. Well within limits. No throttling required.
Constraints
The bot must be invited to any private channel it posts to. Block Kit interactive messages expire after 3 seconds if Slack does not receive an HTTP 200 acknowledgement from the interaction endpoint; the orchestration layer must respond with 200 immediately and process the action asynchronously. Slack does not guarantee delivery ordering; do not rely on message sequence for state tracking.
// Output (approval request to sales manager)
POST https://slack.com/api/chat.postMessage
{ channel: SLACK_APPROVER_USER_ID, blocks: [ section(deal_name, contract_value, expiry_date), actions([ button('Approve', value: deal_id), button('Decline', value: deal_id) ]) ] }
// Input (interaction callback)
POST {orchestration_interaction_endpoint}
Header: X-Slack-Signature, X-Slack-Request-Timestamp
Body: payload={ type:'block_actions', actions:[{ action_id:'approve'|'decline', value: deal_id }], user:{ id, name } }Xero
Used by Agent 3 (Approval and Closing Agent) to create a recurring invoice in the correct Xero account when DocuSign returns a signed envelope status. The invoice is matched to the client contact and a pre-configured renewal product line item.
Auth method
OAuth 2.0 Authorization Code flow with PKCE. After initial authorisation, store the access token as XERO_ACCESS_TOKEN and the refresh token as XERO_REFRESH_TOKEN. Access tokens expire after 1800 seconds; the orchestration layer must refresh using POST https://identity.xero.com/connect/token with grant_type=refresh_token. Store the active Xero tenant (organisation) ID as XERO_TENANT_ID.
Required scopes
openid, profile, email, accounting.transactions, accounting.contacts.read, accounting.settings.read, offline_access
Webhook / trigger setup
No inbound webhook required from Xero. The Xero invoice creation step is triggered internally by the orchestration layer when it receives the DocuSign envelope-completed event. If future monitoring of Xero payment status is needed, Xero Webhooks can be subscribed to via the Xero developer portal (validate with X-Xero-Signature HMAC-SHA256 header using the webhook key stored as XERO_WEBHOOK_KEY).
Required configuration
A standard renewal line item (inventory item) must be pre-created in Xero with a fixed ItemCode stored as XERO_RENEWAL_ITEM_CODE in the credential store. For variable pricing, the unit amount is passed dynamically from the HubSpot deal value field. The Xero contact ID for each client must be resolvable by matching the HubSpot company name or email domain; store a fallback lookup strategy in the orchestration config. Invoice due date is calculated as contract_start_date + 14 days by default; this is configurable.
Rate limits
Xero API: 60 requests per minute, 5,000 requests per day (standard). At 30 to 50 invoices per month the automation makes 2 to 3 API calls per invoice (contact lookup, invoice create, optional send). Total monthly usage is under 150 calls. No throttling required.
Constraints
Xero OAuth refresh tokens expire after 60 days if unused; implement a keep-alive refresh on a 30-day schedule. Xero does not support upsert on invoices; if an invoice already exists for the period, the create call will produce a duplicate. The orchestration layer must check for an existing invoice reference before creating. Custom pricing must route to a human reviewer; automated invoice creation should only fire for standard line-item renewals.
// Input (create invoice)
POST https://api.xero.com/api.xro/2.0/Invoices
Header: Xero-tenant-id: {XERO_TENANT_ID}, Authorization: Bearer {XERO_ACCESS_TOKEN}
Body: { Type:'ACCREC', Contact:{ ContactID: resolved_contact_id }, LineItems:[{ ItemCode: XERO_RENEWAL_ITEM_CODE, Quantity:1, UnitAmount: deal_value }], DueDate: contract_start_date+14d, Reference: hubspot_deal_id }
// Output
{ InvoiceID, InvoiceNumber, Status:'AUTHORISED', AmountDue, DueDate }Google Sheets
Used by Agent 1 (Contract Monitoring Agent) to write the daily prioritised renewal queue as a structured sheet that account managers can review at a glance. The sheet acts as a read-only summary output; the CRM remains the source of truth.
Auth method
OAuth 2.0 using a Google Workspace service account with the Sheets API enabled. Download the service account JSON key and store it as GOOGLE_SERVICE_ACCOUNT_KEY in the credential store. Grant the service account Editor access to the target spreadsheet. Do not use personal OAuth credentials for this integration.
Required scopes
https://www.googleapis.com/auth/spreadsheets, https://www.googleapis.com/auth/drive.file
Webhook / trigger setup
No inbound webhook. The sheet write is a push-only step triggered by the orchestration layer after the daily HubSpot scan and prioritisation logic completes. The sheet is overwritten (not appended) each morning to reflect the current day's queue.
Required configuration
The target spreadsheet ID is stored as GOOGLE_SHEET_RENEWAL_QUEUE_ID in the credential store. The sheet must contain a tab named RenewalQueue with the following header row in columns A through H: Deal ID, Client Name, Expiry Date, Days Remaining, Contract Value, Account Manager, Sequence Stage, Last Activity Date. The header row must not be overwritten; data is written from row 2 downward. Clear the data range before each daily write.
Rate limits
Sheets API: 300 requests per minute per project, 60 requests per minute per user. A daily queue of 50 contracts writes in a single batchUpdate call. No throttling required.
Constraints
Service account writes are not attributed to a named user in the sheet edit history, which is expected behaviour. Do not enable sharing beyond the account manager team. The sheet is a display artefact only; never read from it back into the automation logic.
// Output (daily queue write)
POST https://sheets.googleapis.com/v4/spreadsheets/{GOOGLE_SHEET_RENEWAL_QUEUE_ID}/values/RenewalQueue!A2:H{n}:clear
POST https://sheets.googleapis.com/v4/spreadsheets/{GOOGLE_SHEET_RENEWAL_QUEUE_ID}/values/RenewalQueue!A2:H{n}?valueInputOption=USER_ENTERED
Body: { values: [ [deal_id, client_name, expiry_date, days_remaining, contract_value, account_manager, sequence_stage, last_activity_date], ... ] }Integration and API SpecPage 2 of 4
FS-DOC-05Technical
03Field mappings between tools
The following tables define the exact field mappings at each agent handoff. Source and destination field names are given in monospace format exactly as they appear in the respective tool APIs. These mappings must be implemented in the orchestration layer and not derived at runtime from display names.
Agent 1 handoff: HubSpot to Google Sheets (Contract Monitoring Agent daily queue write)
Source tool
Source field
Destination tool
Destination field
HubSpot
`properties.hs_object_id`
Google Sheets
Column A: Deal ID
HubSpot
`properties.dealname`
Google Sheets
Column B: Client Name
HubSpot
`properties.contract_expiry_date`
Google Sheets
Column C: Expiry Date
HubSpot
Computed: days between today and `contract_expiry_date`
Google Sheets
Column D: Days Remaining
HubSpot
`properties.contract_value_usd`
Google Sheets
Column E: Contract Value
HubSpot
`properties.assigned_account_manager_email`
Google Sheets
Column F: Account Manager
HubSpot
`properties.renewal_sequence_stage`
Google Sheets
Column G: Sequence Stage
HubSpot
`properties.notes_last_updated`
Google Sheets
Column H: Last Activity Date
Agent 2 handoff: HubSpot to Gmail (Renewal Outreach Agent email send)
Source tool
Source field
Destination tool
Destination field
HubSpot
`properties.dealname`
Gmail
Template placeholder: `{{deal_name}}`
HubSpot
`associations.contacts[0].properties.firstname`
Gmail
Template placeholder: `{{client_first_name}}`
HubSpot
`associations.contacts[0].properties.email`
Gmail
MIME To: header
HubSpot
`properties.contract_expiry_date`
Gmail
Template placeholder: `{{contract_expiry_date}}`
HubSpot
`properties.assigned_account_manager_email`
Gmail
MIME From: header (send-as address)
HubSpot
`properties.hubspot_owner_id` -> owner name lookup
Gmail
Template placeholder: `{{account_manager_name}}`
Agent 2 handoff: Gmail reply detection to HubSpot (pause sequence flag)
Source tool
Source field
Destination tool
Destination field
Gmail
Message header: `In-Reply-To` matched to open thread ID
HubSpot
`properties.renewal_sequence_stage` set to `paused_reply`
Gmail
Message header: `From` (client email)
HubSpot
`properties.notes_last_updated` (activity timestamp)
Gmail
`id` (Gmail message ID)
HubSpot
Timeline event: `externalId` for deduplication
Agent 3 handoff: HubSpot and Slack approval to DocuSign (contract generation)
Source tool
Source field
Destination tool
Destination field
HubSpot
`properties.dealname`
DocuSign
Tab: `deal_name`
HubSpot
`associations.contacts[0].properties.firstname` + `lastname`
DocuSign
Tab: `client_name`
HubSpot
`associations.contacts[0].properties.email`
DocuSign
Recipient: `email` (roleName: Client)
HubSpot
`properties.contract_expiry_date`
DocuSign
Tab: `contract_end_date`
HubSpot
Computed: `contract_expiry_date` + 1 year
DocuSign
Tab: `contract_start_date` (new term)
HubSpot
`properties.contract_value_usd`
DocuSign
Tab: `annual_value`
HubSpot
`properties.assigned_account_manager_email` -> name lookup
DocuSign
Tab: `account_manager_name`
Slack
`actions[0].value` (deal_id)
DocuSign
Envelope custom field: `hubspot_deal_id` (for post-sign callback matching)
Agent 3 handoff: DocuSign signed event to HubSpot and Xero (post-signature updates)
Source tool
Source field
Destination tool
Destination field
DocuSign
`envelopeId`
HubSpot
Timeline event `externalId`; also stored in `properties.docusign_envelope_id`
DocuSign
`completedDateTime`
HubSpot
`properties.renewal_signed_date`
DocuSign
Envelope custom field: `hubspot_deal_id`
HubSpot
`hs_object_id` (used to identify the deal to update)
DocuSign
`completedDateTime`
HubSpot
`properties.dealstage` set to `renewed`
DocuSign
Envelope custom field: `hubspot_deal_id` -> HubSpot deal -> `contract_value_usd`
Xero
`LineItems[0].UnitAmount`
DocuSign
Envelope custom field: `hubspot_deal_id` -> HubSpot deal -> `associations.companies[0].name`
Xero
`Contact.Name` (used for contact lookup)
DocuSign
Computed: `completedDateTime` + 14 days
Xero
`DueDate`
DocuSign
Envelope custom field: `hubspot_deal_id`
Xero
`Reference`
04Build stack and orchestration
Orchestration layout
Three separate workflow instances, one per agent: Workflow-1 (Contract Monitoring Agent), Workflow-2 (Renewal Outreach Agent), Workflow-3 (Approval and Closing Agent). All three workflows share a single encrypted credential store. Credentials are referenced by key name; no credential value is embedded in workflow logic. Workflows communicate via shared HubSpot deal properties rather than direct inter-workflow calls, ensuring loose coupling and independent testability.
Workflow-1 trigger (Contract Monitoring Agent)
Scheduled poll. Runs daily at 08:00 in the account manager's local timezone (timezone set in orchestration platform config). No inbound webhook. The poll calls the HubSpot Deals API with a date-range filter. The schedule is configurable without code change.
Workflow-2 trigger (Renewal Outreach Agent)
Event-driven, polling HubSpot for deals where renewal_sequence_stage has changed to a value not equal to its previous state (change detection on a 15-minute poll interval). Alternatively, if the orchestration platform supports HubSpot workflow webhooks natively, configure a HubSpot workflow to POST to the orchestration inbound endpoint on property change. Slack interaction callbacks are received at the platform's inbound webhook endpoint; signature validated using SLACK_SIGNING_SECRET before processing.
Workflow-3 trigger (Approval and Closing Agent)
Inbound webhook. Triggered by: (a) Slack interaction callback when sales manager clicks Approve or Decline; (b) DocuSign Connect webhook on envelope-completed or envelope-declined. Both inbound sources share the same orchestration webhook router, which dispatches by payload schema. DocuSign payloads are validated with HMAC-SHA256 using DOCUSIGN_CONNECT_HMAC_SECRET. Slack payloads are validated with HMAC-SHA256 using SLACK_SIGNING_SECRET.
Signature validation
All inbound webhooks (DocuSign Connect, Slack Interactivity) must be validated before any processing occurs. Reject and log any payload that fails signature validation. Return HTTP 200 immediately on receipt to prevent delivery timeouts; process asynchronously.
Credential store
Platform-native encrypted secrets store. All keys below are stored here. No values appear in workflow logic, environment files committed to version control, or logs.
All values are illustrative placeholders. Replace with real credentials during environment setup. Never log these values.
// Credential store contents
// HubSpot
HUBSPOT_PRIVATE_APP_TOKEN = 'pat-na1-...'
HUBSPOT_PORTAL_ID = '12345678'
// Gmail (per account manager; repeat pattern for each user)
GMAIL_ACCESS_TOKEN_AM1 = 'ya29...'
GMAIL_REFRESH_TOKEN_AM1 = '1//0e...'
GMAIL_ACCESS_TOKEN_AM2 = 'ya29...'
GMAIL_REFRESH_TOKEN_AM2 = '1//0e...'
// Gmail email templates (stored as config, not code)
RENEWAL_TEMPLATE_90D = '<template string with {{placeholders}}'
RENEWAL_TEMPLATE_60D = '<template string with {{placeholders}}'
RENEWAL_TEMPLATE_30D = '<template string with {{placeholders}}'
// DocuSign
DOCUSIGN_INTEGRATION_KEY = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
DOCUSIGN_PRIVATE_KEY = '-----BEGIN RSA PRIVATE KEY-----\n...'
DOCUSIGN_IMPERSONATION_USER_GUID = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
DOCUSIGN_ACCOUNT_ID = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
DOCUSIGN_RENEWAL_TEMPLATE_ID = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
DOCUSIGN_CONNECT_HMAC_SECRET = 'abc123...'
// Slack
SLACK_BOT_TOKEN = 'xoxb-...'
SLACK_SIGNING_SECRET = 'abc123...'
SLACK_APPROVER_USER_ID = 'U0123456789'
SLACK_RENEWALS_CHANNEL_ID = 'C0123456789'
// Xero
XERO_ACCESS_TOKEN = 'eyJ...'
XERO_REFRESH_TOKEN = 'abc123...'
XERO_TENANT_ID = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
XERO_RENEWAL_ITEM_CODE = 'RENEWAL-STD'
XERO_WEBHOOK_KEY = 'abc123...'
// Google Sheets
GOOGLE_SERVICE_ACCOUNT_KEY = '{ "type": "service_account", ... }'
GOOGLE_SHEET_RENEWAL_QUEUE_ID = '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms'Rotate all credentials immediately if any are exposed in logs, version control, or error messages. The credential store must be backed up to a secure location before any platform migration. The FullSpec team can advise on rotation procedures via support@gofullspec.com.
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 are written to the orchestration platform's error log with the deal ID, tool name, error code, timestamp, and retry attempt number. Where a manual fallback is required, a Slack alert is sent to SLACK_RENEWALS_CHANNEL_ID with enough context for a person to complete the step.
Integration
Scenario
Required behaviour
HubSpot (Workflow-1 scan)
API returns 429 rate limit
Pause execution for 10 seconds, then retry. After 3 consecutive 429 responses, halt the scan run and send a Slack alert to SLACK_RENEWALS_CHANNEL_ID. Log the incident. Retry the full scan at the next scheduled run.
HubSpot (Workflow-1 scan)
API returns 500 or 503
Retry with exponential backoff: 30 s, 2 min, 5 min (3 attempts). If all attempts fail, send Slack alert and log. Do not mark any deals as processed for that run. The next scheduled run will re-attempt.
HubSpot (activity log write)
PATCH to deal record returns 404 (deal not found)
Log the missing deal ID and send a Slack alert with deal ID and deal name. Do not retry. A human must investigate whether the deal was deleted or merged in HubSpot.
Gmail (send renewal email)
OAuth access token expired
Automatically refresh using stored refresh token before every send attempt. If the refresh call fails (invalid_grant), halt that account manager's send queue, log the error, and send a Slack DM to the affected account manager with instructions to re-authorise.
Gmail (send renewal email)
Send returns 400 or 403 (bad request or permission denied)
Do not retry. Log the error with the deal ID and recipient email. Send a Slack alert. The account manager must send the email manually. Update renewal_sequence_stage to manual_intervention_required in HubSpot.
Gmail (reply detection)
Pub/Sub push notification missed or delayed
Implement a fallback 30-minute polling job on each active renewal thread using messages.list with q='in:inbox is:unread'. If a reply is detected via polling that was missed by Pub/Sub, apply the paused_reply flag as normal. Log the Pub/Sub miss for monitoring.
DocuSign (envelope send)
Template not found (404) or tab mapping error (400)
Do not retry. Log the full error response. Send Slack alert to SLACK_RENEWALS_CHANNEL_ID with deal ID and error detail. The account manager must send the contract manually via DocuSign. Mark deal property docusign_send_status as failed in HubSpot.
DocuSign (Connect webhook)
Inbound webhook fails HMAC validation
Reject the payload with HTTP 400. Log the source IP, timestamp, and raw payload hash. Do not process. Alert the FullSpec build team via support@gofullspec.com if this occurs more than twice in a 24-hour window, as it may indicate a misconfigured Connect endpoint or a security event.
DocuSign (Connect webhook)
Webhook delivery fails (orchestration endpoint returns non-200)
DocuSign Connect will retry delivery up to 10 times over 24 hours using its built-in retry schedule. Ensure the orchestration endpoint returns HTTP 200 immediately on receipt and processes asynchronously. If all DocuSign retries are exhausted, DocuSign sends a failure notification email to the configured Connect admin address; monitor this inbox.
Slack (approval message send)
API returns 429 or network timeout
Retry after 5 seconds, then 30 seconds (2 attempts). If both fail, log and send an email fallback to the sales manager's address (resolved from SLACK_APPROVER_USER_ID via users.info API, cached at startup). The email must contain the same deal summary and a link to the HubSpot deal for manual approval.
Xero (invoice creation)
Duplicate invoice detected (reference already exists)
Do not create a second invoice. Log the duplicate attempt with the deal ID and existing Xero invoice reference. Send a Slack alert to SLACK_RENEWALS_CHANNEL_ID. A human must review whether the existing invoice is correct or needs amendment.
Xero (invoice creation)
Contact not found in Xero (no match on company name or email domain)
Do not create the invoice. Log the unmatched contact name and deal ID. Send Slack alert with instructions for the account manager to create the contact in Xero manually and re-trigger the invoice step via a workflow flag on the HubSpot deal (invoice_retry_requested = true).
Google Sheets (queue write)
Write returns 403 (service account lost access) or 404 (sheet not found)
Log the error and send Slack alert to SLACK_RENEWALS_CHANNEL_ID. The daily queue will not be visible to account managers; they must use HubSpot directly until the sheet access is restored. Do not halt the HubSpot scan or outreach steps; they are not dependent on the sheet write.
All integrations
Unhandled exception or unexpected response schema
Catch all unhandled exceptions at the workflow level. Log the full stack trace, deal ID if available, tool name, and timestamp. Send a Slack alert to SLACK_RENEWALS_CHANNEL_ID and an email to support@gofullspec.com. Halt the affected workflow step. Never allow silent failure or partial state writes that cannot be rolled back.
All Slack alerts sent by error handlers must include: the deal ID, the tool that failed, the error code or message, the time of failure, and a direct HubSpot link to the affected deal record. Generic alerts without deal context are not acceptable. Contact support@gofullspec.com for error escalation outside business hours.
Integration and API SpecPage 4 of 4