FS-DOC-05Technical
Integration and API Spec
Proposal Creation Automation
[YourCompany.com] · Sales Department · Prepared by FullSpec · [Today's Date]
This document is the authoritative technical reference for every integration point in the Proposal Creation automation. It covers tool authentication, required OAuth scopes, webhook configuration, field mappings between agents, credential store layout, orchestration architecture, and defined failure behaviours. The FullSpec team uses this specification as the build source of truth. No integration credential should be hardcoded; all secrets are stored in the central credential store as described in Section 04.
01Tool inventory
Tool
Role in automation
Auth method
Min plan required
Used by agent(s)
HubSpot
Deal data source, CRM stage updates, activity logging
OAuth 2.0 / Private App token
Sales Hub Starter ($20/mo minimum for webhook access)
Agent 1, Agent 2
PandaDoc
Proposal template population and document creation
API Key (Bearer token)
Business plan ($49/mo) for API access
Agent 1, Agent 2
Google Docs / Sheets
Pricing tier and discount rule source
OAuth 2.0 (service account)
Google Workspace (any paid tier) or free personal account
Agent 1
Slack
Manager approval routing via interactive message buttons
OAuth 2.0 / Bot token (xoxb-)
Free tier sufficient; Pro required for message retention >90 days
Agent 2
Gmail
Proposal delivery to prospect via personalised email
OAuth 2.0 (gmail.send scope)
Any Google Workspace account with SMTP/API access enabled
Agent 2
Automation platform (orchestration layer)
Workflow orchestration connecting all tools; hosts triggers, logic, and credential store
Internal credential store; per-connector OAuth or API key
Plan supporting webhooks, multi-step workflows, and credential vaults
All agents
Before you connect anything: sandbox-test every integration using non-production credentials and a HubSpot sandbox account before any production credentials are entered. Verify each connection returns a successful status response before moving to the next tool. This prevents live deal data from being touched during build and test phases.
02Per-tool integration detail
HubSpot
Serves as the automation trigger source and the final write destination. A deal moving to the 'Proposal Requested' pipeline stage fires the inbound webhook. At the end of the flow, the Approval and Delivery Agent writes the updated deal stage and logs a timestamped activity. Estimated integration complexity: Moderate.
Auth method
OAuth 2.0 Private App token. Create a Private App in HubSpot Settings > Integrations > Private Apps. Store the generated token as HUBSPOT_PRIVATE_APP_TOKEN in the credential store. Do not use legacy API keys.
Required scopes
crm.objects.deals.read, crm.objects.deals.write, crm.objects.contacts.read, crm.objects.companies.read, timeline.events.write, webhooks.read
Webhook / trigger setup
Create a webhook subscription in HubSpot Settings > Integrations > Webhooks. Subscribe to the event type: deal.propertyChange with propertyName: dealstage. Filter on the target stage ID for 'Proposal Requested' (store this stage ID as HUBSPOT_STAGE_ID_PROPOSAL_REQUESTED in the credential store). HubSpot signs each webhook payload with HMAC-SHA256 using your app client secret; validate the X-HubSpot-Signature-Version header (version v2) on every inbound request before processing.
Required configuration
Deal pipeline ID (HUBSPOT_PIPELINE_ID) and stage IDs for 'Proposal Requested' (HUBSPOT_STAGE_ID_PROPOSAL_REQUESTED) and 'Proposal Sent' (HUBSPOT_STAGE_ID_PROPOSAL_SENT) must be stored in the credential store. Required deal properties to read: dealname, amount, dealstage, hubspot_owner_id, associated contact ID, associated company ID, hs_product_ids, custom field hs_custom_pricing_tier, custom field hs_custom_discount_pct. Confirm exact internal field names against the live HubSpot account before build.
Rate limits
HubSpot Private App: 110 requests per 10 seconds per token (burst); 40,000 requests per day. At 20 proposals per month (~1 per business hour), peak load is well below limits. Throttling is not required at current volume, but implement exponential backoff on 429 responses regardless.
Constraints
Webhook deliveries are not guaranteed exactly-once; implement idempotency using the HubSpot object ID (hs_object_id) to prevent duplicate proposal generation. Deal properties with null or empty values must be caught before PandaDoc generation and routed to the missing-field error path.
// Inbound webhook payload (abbreviated)
{
"objectId": "<deal_id>",
"propertyName": "dealstage",
"propertyValue": "<stage_id_proposal_requested>",
"subscriptionType": "deal.propertyChange"
}
// GET /crm/v3/objects/deals/{deal_id}?properties=dealname,amount,hubspot_owner_id,hs_custom_pricing_tier,hs_custom_discount_pct
// GET /crm/v3/objects/deals/{deal_id}/associations/contacts
// GET /crm/v3/objects/deals/{deal_id}/associations/companies
// PATCH /crm/v3/objects/deals/{deal_id} — update stage to Proposal Sent
// POST /crm/v3/timeline/events — log proposal send activityPandaDoc
Receives the structured deal data object from the Proposal Assembly Agent and generates a new document from the stored master template. Returns a document ID and shareable link. The Approval and Delivery Agent later retrieves the document status to confirm it has been sent. Estimated integration complexity: Moderate.
Auth method
API Key passed as Bearer token in the Authorization header. Generate the key in PandaDoc Settings > Integrations > API. Store as PANDADOC_API_KEY in the credential store.
Required scopes
PandaDoc API keys are not scope-segmented by default. Ensure the API key is generated by an account with the role of Manager or Admin to allow document creation, sending, and status reads.
Webhook / trigger setup
Configure a PandaDoc webhook in Settings > Integrations > Webhooks to receive document_state_changed events. Subscribe to statuses: document.draft, document.sent, document.viewed, document.completed. Store the webhook endpoint URL and verify the X-PandaDoc-Signature header using the shared secret stored as PANDADOC_WEBHOOK_SECRET.
Required configuration
Master template ID must be stored as PANDADOC_TEMPLATE_ID. Template variable names must exactly match the field keys passed by the Proposal Assembly Agent (see Section 03 for the full mapping). Recipient role name in the template must be 'Prospect' (exact string). Do not hardcode the template ID in workflow logic.
Rate limits
PandaDoc Business plan: 100 API requests per minute. At current volume of 20 proposals per month, average load is far below this ceiling. No throttling required, but add retry logic on 429 responses with a 10-second initial backoff.
Constraints
Documents created via API start in draft status and must be explicitly sent or shared via a subsequent API call. The shareable link (view_link) is returned in the document creation response and used in the Slack approval message. Variables left unpopulated in the template will render as blank fields in the PDF; the agent must validate all required variables are present before calling the create endpoint.
// POST /public/v1/documents
{
"name": "Proposal - {{deal.dealname}} - {{date}}",
"template_uuid": "{{PANDADOC_TEMPLATE_ID}}",
"recipients": [{
"email": "{{contact.email}}",
"first_name": "{{contact.firstname}}",
"last_name": "{{contact.lastname}}",
"role": "Prospect"
}],
"fields": {
"prospect_name": { "value": "{{contact.firstname}} {{contact.lastname}}" },
"company_name": { "value": "{{company.name}}" },
"deal_value": { "value": "{{deal.amount}}" },
"pricing_tier": { "value": "{{resolved.pricing_tier}}" },
"discount_pct": { "value": "{{resolved.discount_pct}}" },
"expiry_date": { "value": "{{resolved.expiry_date}}" },
"scope_description": { "value": "{{resolved.scope_description}}" }
}
}
// Response — store document_id and view_link
{ "id": "<document_id>", "view_link": "https://app.pandadoc.com/..." }Google Docs / Sheets
Read-only source of pricing tier rules, discount thresholds, and product configurations. The Proposal Assembly Agent reads the canonical pricing sheet each time a proposal is triggered, ensuring the latest approved rates are always used. Write access is not required. Estimated integration complexity: Simple.
Auth method
OAuth 2.0 using a Google service account. Generate a service account in Google Cloud Console, download the JSON key, and share the pricing sheet with the service account email address (view-only). Store the JSON key contents as GOOGLE_SERVICE_ACCOUNT_JSON in the credential store.
Required scopes
https://www.googleapis.com/auth/spreadsheets.readonly, https://www.googleapis.com/auth/drive.readonly
Webhook / trigger setup
Not applicable. The automation polls the sheet on demand at proposal trigger time using the Sheets API. No webhook is required.
Required configuration
Pricing spreadsheet ID stored as GOOGLE_PRICING_SHEET_ID. Target sheet tab name stored as GOOGLE_PRICING_TAB_NAME (e.g. 'Pricing_v2'). Named range for the pricing table stored as GOOGLE_PRICING_NAMED_RANGE. Do not reference sheets by tab index; always use the named range to survive tab reordering.
Rate limits
Google Sheets API: 300 read requests per minute per project. At 20 proposals per month, this is negligible. No throttling needed.
Constraints
The pricing sheet must be kept in a structured tabular format with consistent column headers. If the sheet structure changes (columns added, renamed, or reordered), the field mapping in the Proposal Assembly Agent must be updated to match. The FullSpec team should document the expected column schema and flag any deviation as a breaking change.
// GET https://sheets.googleapis.com/v4/spreadsheets/{GOOGLE_PRICING_SHEET_ID}/values/{GOOGLE_PRICING_NAMED_RANGE}
// Headers: Authorization: Bearer <service_account_access_token>
// Expected response structure
{
"values": [
["tier_name", "min_value", "max_value", "base_price", "max_discount_pct", "scope_description"],
["Starter", "0", "4999", "1200", "10", "Core package..."],
["Growth", "5000", "14999", "2800", "15", "Full suite..."],
["Enterprise","15000", "999999", "5500", "20", "Custom scope..."]
]
}Integration and API SpecPage 1 of 3
FS-DOC-05Technical
Slack
Hosts the manager approval interaction. The Approval and Delivery Agent posts a structured Block Kit message to the designated approval channel containing a deal summary, the PandaDoc preview link, and two interactive buttons: Approve and Request Changes. The platform listens for the interactive payload on the configured interactivity endpoint. Estimated integration complexity: Moderate.
Auth method
OAuth 2.0 Bot token (xoxb- prefix). Create a Slack app at api.slack.com/apps, install it to the workspace, and store the Bot User OAuth Token as SLACK_BOT_TOKEN. Store the Signing Secret as SLACK_SIGNING_SECRET for payload verification.
Required scopes
chat:write, chat:write.public, channels:read, im:write, users:read, users:read.email (Bot Token Scopes). No user token scopes required.
Webhook / trigger setup
Enable Interactivity in the Slack app settings. Set the Request URL to the automation platform's inbound webhook endpoint for handling button click payloads. Validate every interactive payload by verifying the X-Slack-Signature header using HMAC-SHA256 with SLACK_SIGNING_SECRET before processing. The approval channel ID must be stored as SLACK_APPROVAL_CHANNEL_ID (do not hardcode the channel name; resolve by ID).
Required configuration
SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET, and SLACK_APPROVAL_CHANNEL_ID stored in the credential store. The sales manager's Slack user ID stored as SLACK_MANAGER_USER_ID for direct @mention in the Block Kit message. Block Kit message template is defined in the workflow; it is not hardcoded in the Slack app configuration.
Rate limits
Slack Web API: Tier 3 rate limit of 50 requests per minute for chat.postMessage. At 20 proposals per month, load is trivial. No throttling required. Implement retry with 1-second delay on 429 responses.
Constraints
Interactive payload callbacks must respond within 3 seconds with a 200 OK, otherwise Slack retries and displays an error to the user. If the downstream processing takes longer, respond immediately with 200 and process the action asynchronously. Slack message payloads expire after 30 minutes for response_url usage; use the API for updates instead.
// POST https://slack.com/api/chat.postMessage
{
"channel": "{{SLACK_APPROVAL_CHANNEL_ID}}",
"text": "Proposal ready for approval: {{deal.dealname}}",
"blocks": [
{ "type": "section", "text": { "type": "mrkdwn",
"text": "*<@{{SLACK_MANAGER_USER_ID}}>* — proposal ready for {{contact.firstname}} {{contact.lastname}} ({{company.name}})\nDeal value: ${{deal.amount}} | Tier: {{resolved.pricing_tier}}\n<{{pandadoc.view_link}}|Preview proposal>" }
},
{ "type": "actions", "elements": [
{ "type": "button", "text": { "type": "plain_text", "text": "Approve" },
"style": "primary", "action_id": "proposal_approve", "value": "{{deal.hs_object_id}}" },
{ "type": "button", "text": { "type": "plain_text", "text": "Request Changes" },
"style": "danger", "action_id": "proposal_changes", "value": "{{deal.hs_object_id}}" }
]}
]
}
// Inbound interactive payload (button click)
{ "payload": { "type": "block_actions", "actions": [{ "action_id": "proposal_approve|proposal_changes", "value": "<deal_id>" }], "user": { "id": "<slack_user_id>" } } }Gmail
Delivers the approved PandaDoc proposal link to the prospect via a personalised email. The Approval and Delivery Agent composes the message using a pre-approved template, substitutes prospect and deal variables, and sends via the Gmail API. Estimated integration complexity: Simple.
Auth method
OAuth 2.0. The sending Gmail account must complete the OAuth consent flow and grant the required scope. Store the refresh token as GMAIL_OAUTH_REFRESH_TOKEN and the client credentials as GMAIL_CLIENT_ID and GMAIL_CLIENT_SECRET in the credential store. Access tokens are refreshed automatically by the orchestration layer.
Required scopes
https://www.googleapis.com/auth/gmail.send (send-only; do not request gmail.readonly or gmail.modify unless explicitly needed for a future feature)
Webhook / trigger setup
Not applicable. Gmail is an outbound-only integration in this automation. No inbound webhook is required. Sending is triggered by the approval confirmation event from the Slack interactive payload.
Required configuration
Sending account address stored as GMAIL_SENDER_ADDRESS. Email subject line template and body template stored as workflow variables (not hardcoded in the credential store). The PandaDoc view_link is injected at send time. The from display name is stored as GMAIL_SENDER_DISPLAY_NAME.
Rate limits
Gmail API: 250 quota units per user per second; sending 1 email costs 100 units. Daily sending limit is 2,000 messages for Workspace accounts. At 20 proposals per month, load is negligible. No throttling required.
Constraints
The OAuth refresh token expires if the authorised account password changes or access is revoked. Monitor for invalid_grant errors and alert via the error handling path. Emails must comply with the authorised account's Workspace sending policies. Do not send from an alias unless that alias is configured as a send-as address in the Gmail account settings.
// POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
// Headers: Authorization: Bearer <access_token>
{
"raw": "<base64url-encoded RFC 2822 message>"
}
// Decoded message structure
From: {{GMAIL_SENDER_DISPLAY_NAME}} <{{GMAIL_SENDER_ADDRESS}}>
To: {{contact.email}}
Subject: Your proposal from [YourCompany.com] — {{company.name}}
Content-Type: text/html
Hi {{contact.firstname}},
Please find your tailored proposal here: {{pandadoc.view_link}}
This proposal is valid until {{resolved.expiry_date}}.
...03Field mappings between tools
The following tables define the exact field translations at each agent handoff. All field names are shown in monospace to match their exact API or template representations. Any deviation between these names and the live configuration is a breaking change.
Handoff A: HubSpot to Proposal Assembly Agent (Agent 1 internal data object)
Source tool
Source field
Destination
Destination field
HubSpot
hs_object_id
Agent 1 data object
deal.hs_object_id
HubSpot
dealname
Agent 1 data object
deal.dealname
HubSpot
amount
Agent 1 data object
deal.amount
HubSpot
dealstage
Agent 1 data object
deal.dealstage
HubSpot
hubspot_owner_id
Agent 1 data object
deal.hubspot_owner_id
HubSpot
hs_custom_pricing_tier
Agent 1 data object
deal.pricing_tier_raw
HubSpot
hs_custom_discount_pct
Agent 1 data object
deal.discount_pct_raw
HubSpot (contact)
firstname
Agent 1 data object
contact.firstname
HubSpot (contact)
lastname
Agent 1 data object
contact.lastname
HubSpot (contact)
email
Agent 1 data object
contact.email
HubSpot (company)
name
Agent 1 data object
company.name
Handoff B: Google Sheets pricing resolution to Agent 1 data object (resolved fields)
Source tool
Source field
Destination
Destination field
Google Sheets
tier_name
Agent 1 data object
resolved.pricing_tier
Google Sheets
base_price
Agent 1 data object
resolved.base_price
Google Sheets
max_discount_pct
Agent 1 data object
resolved.max_discount_pct
Google Sheets
scope_description
Agent 1 data object
resolved.scope_description
Agent 1 logic
computed: expiry = today + 30d
Agent 1 data object
resolved.expiry_date
Handoff C: Agent 1 data object to PandaDoc template variables
Source tool
Source field
Destination tool
Destination field (template variable)
Agent 1 data object
contact.firstname
PandaDoc
prospect_name
Agent 1 data object
contact.lastname
PandaDoc
prospect_name
Agent 1 data object
company.name
PandaDoc
company_name
Agent 1 data object
deal.amount
PandaDoc
deal_value
Agent 1 data object
resolved.pricing_tier
PandaDoc
pricing_tier
Agent 1 data object
resolved.base_price
PandaDoc
base_price
Agent 1 data object
resolved.max_discount_pct
PandaDoc
discount_pct
Agent 1 data object
resolved.scope_description
PandaDoc
scope_description
Agent 1 data object
resolved.expiry_date
PandaDoc
expiry_date
Handoff D: PandaDoc document creation response to Agent 2 (Approval and Delivery Agent)
Source tool
Source field
Destination tool
Destination field
PandaDoc API response
id
Agent 2 data object
pandadoc.document_id
PandaDoc API response
view_link
Agent 2 data object
pandadoc.view_link
Agent 1 data object (passed through)
deal.hs_object_id
Agent 2 data object
deal.hs_object_id
Agent 1 data object (passed through)
contact.email
Agent 2 data object
contact.email
Agent 1 data object (passed through)
contact.firstname
Agent 2 data object
contact.firstname
Agent 1 data object (passed through)
company.name
Agent 2 data object
company.name
Agent 1 data object (passed through)
resolved.expiry_date
Agent 2 data object
resolved.expiry_date
Handoff E: Agent 2 to HubSpot (final CRM write)
Source tool
Source field
Destination tool
Destination field
Agent 2 data object
HUBSPOT_STAGE_ID_PROPOSAL_SENT
HubSpot
dealstage
Agent 2 data object
pandadoc.document_id
HubSpot
hs_custom_pandadoc_doc_id
Agent 2 data object
timestamp (ISO 8601)
HubSpot
timeline event: timestamp
Agent 2 data object
"Proposal sent via automation"
HubSpot
timeline event: body
All field names in the tables above are case-sensitive. A mismatch between a PandaDoc template variable name and the field key sent via the API will silently render as a blank in the generated document. Validate every variable mapping against the live PandaDoc template before the first production run.
Integration and API SpecPage 2 of 3
FS-DOC-05Technical
04Build stack and orchestration
Orchestration layout
Two discrete workflows, one per agent (Proposal Assembly Agent and Approval and Delivery Agent). Each workflow is independently deployable and version-controlled. Both share a single central credential store; no credentials are duplicated across workflows.
Agent 1 trigger mechanism
Inbound webhook (push). HubSpot delivers a signed webhook payload to the automation platform endpoint when the deal stage changes to 'Proposal Requested'. The platform validates the HMAC-SHA256 signature using HUBSPOT_WEBHOOK_CLIENT_SECRET before executing the workflow. No polling is used for this trigger.
Agent 2 trigger mechanism
Internal event (push from Agent 1). Agent 1 emits a structured completion event containing the full data object once the PandaDoc document is created. Agent 2 subscribes to this internal event and activates immediately. Additionally, Agent 2 listens on a second inbound webhook endpoint for Slack interactive payload callbacks (button clicks). Slack payload signatures are validated using SLACK_SIGNING_SECRET on every inbound request before branching logic executes.
Idempotency strategy
Both workflows use deal.hs_object_id as the idempotency key. On each trigger, the platform checks a short-lived deduplication store (TTL: 10 minutes) before executing. Duplicate webhook deliveries for the same deal ID within the TTL window are dropped with a 200 OK response and no further action.
Credential store location
All secrets are stored in the automation platform's native encrypted credential store (environment variables or a secrets vault depending on the chosen platform). No credential appears in workflow logic, logs, or documentation in plaintext. Access is restricted to the automation service account only.
Logging and observability
Each workflow step emits a structured log entry: timestamp, workflow ID, step name, deal hs_object_id, and status (success, retrying, or failed). Logs are retained for a minimum of 30 days. Failed executions trigger an alert to support@gofullspec.com and the designated internal alert channel (SLACK_ALERT_CHANNEL_ID).
The following code block lists every credential that must be present in the credential store before either workflow can execute in production. Each entry shows the key name, the source system, and the purpose.
Full credential store manifest — populate all entries before any production execution
# ============================================================
# CREDENTIAL STORE — Proposal Creation Automation
# All values stored as encrypted secrets. Never log or expose.
# ============================================================
# HubSpot
HUBSPOT_PRIVATE_APP_TOKEN # HubSpot Private App — API auth for all CRM reads and writes
HUBSPOT_WEBHOOK_CLIENT_SECRET # HubSpot app client secret — HMAC-SHA256 webhook signature validation
HUBSPOT_PIPELINE_ID # Internal ID of the sales pipeline containing the target stages
HUBSPOT_STAGE_ID_PROPOSAL_REQUESTED # Deal stage ID for 'Proposal Requested'
HUBSPOT_STAGE_ID_PROPOSAL_SENT # Deal stage ID for 'Proposal Sent'
# PandaDoc
PANDADOC_API_KEY # PandaDoc API key — Bearer token for document create and read
PANDADOC_TEMPLATE_ID # UUID of the master proposal template in PandaDoc
PANDADOC_WEBHOOK_SECRET # Shared secret for verifying inbound PandaDoc webhook signatures
# Google Sheets (service account)
GOOGLE_SERVICE_ACCOUNT_JSON # Full JSON key file contents for the Google service account
GOOGLE_PRICING_SHEET_ID # Spreadsheet ID from the pricing sheet URL
GOOGLE_PRICING_TAB_NAME # Exact tab name containing the pricing table (e.g. 'Pricing_v2')
GOOGLE_PRICING_NAMED_RANGE # Named range for the pricing data table
# Slack
SLACK_BOT_TOKEN # Bot User OAuth Token (xoxb-) — post messages and handle interactions
SLACK_SIGNING_SECRET # App signing secret — validate interactive payload signatures
SLACK_APPROVAL_CHANNEL_ID # Channel ID for the manager approval messages
SLACK_MANAGER_USER_ID # Slack user ID of the sales manager for @mention
SLACK_ALERT_CHANNEL_ID # Channel ID for automation error and alert notifications
# Gmail
GMAIL_CLIENT_ID # OAuth 2.0 client ID for Gmail API
GMAIL_CLIENT_SECRET # OAuth 2.0 client secret for Gmail API
GMAIL_OAUTH_REFRESH_TOKEN # Long-lived refresh token for the sending Gmail account
GMAIL_SENDER_ADDRESS # Full email address of the sending account
GMAIL_SENDER_DISPLAY_NAME # Display name shown in the From field to the prospect
05Error handling and retry logic
Every integration point has a defined failure behaviour. Unhandled exceptions must never fail silently. All failures that are not auto-resolved within the defined retry window must produce a visible alert and a structured log entry. Manual fallback paths are defined for each critical failure.
Integration
Scenario
Required behaviour
HubSpot webhook inbound
Signature validation fails (X-HubSpot-Signature mismatch)
Return 401 immediately. Log the raw headers and payload hash. Do not execute the workflow. Alert SLACK_ALERT_CHANNEL_ID. No retry.
HubSpot — deal property read
One or more required deal fields are null or empty (e.g. amount, contact email, pricing tier)
Halt workflow execution. Post a Slack notification to the rep's manager listing the missing fields and the deal name. Log the deal ID and missing field list. Do not proceed to PandaDoc. Manual fallback: rep completes the fields and re-stages the deal to trigger a fresh run.
HubSpot — deal property read
API returns 401 Unauthorized (token expired or revoked)
Retry once after 5 seconds. If second attempt fails, halt and alert support@gofullspec.com and SLACK_ALERT_CHANNEL_ID. Manual fallback: FullSpec team rotates the private app token and updates HUBSPOT_PRIVATE_APP_TOKEN in the credential store.
HubSpot — deal property read
API returns 429 Too Many Requests
Implement exponential backoff: wait 10s, 20s, 40s for up to 3 retries. If all retries exhausted, halt and alert. Log the retry count and final response code.
Google Sheets — pricing read
Named range not found or sheet tab renamed
Halt workflow. Alert SLACK_ALERT_CHANNEL_ID with the exact error and sheet ID. Do not proceed to PandaDoc. Manual fallback: FullSpec team updates GOOGLE_PRICING_TAB_NAME or GOOGLE_PRICING_NAMED_RANGE in the credential store.
Google Sheets — pricing read
Deal's pricing tier value does not match any row in the pricing table
Halt workflow. Log the unmatched tier value and deal ID. Post a Slack alert to the manager listing the deal and the unresolved tier. Do not generate a proposal with a blank or incorrect price. Manual fallback: rep corrects the hs_custom_pricing_tier field in HubSpot and re-stages.
PandaDoc — document creation
API returns 422 Unprocessable Entity (template variable name mismatch)
Halt workflow. Log the full API error response including the list of unrecognised field keys. Alert support@gofullspec.com. Do not create a partial document. Manual fallback: FullSpec team audits the field mapping in Section 03 against the live template and corrects mismatches.
PandaDoc — document creation
API returns 500 or 503 (PandaDoc service error)
Retry with exponential backoff: wait 15s, 30s, 60s for up to 3 attempts. If all retries exhausted, alert SLACK_ALERT_CHANNEL_ID and log the deal ID and all response codes. Manual fallback: FullSpec team monitors PandaDoc status page and re-triggers once service recovers.
Slack — approval message post
Bot token invalid or bot removed from channel
Retry once after 5 seconds. If second attempt fails, alert support@gofullspec.com via email (do not use Slack as the alert channel in this scenario). Log the deal ID and error. Manual fallback: manager is emailed the PandaDoc view_link directly using Gmail for out-of-band approval.
Slack — interactive payload
No manager response received within 4 business hours
Send a reminder message to SLACK_APPROVAL_CHANNEL_ID mentioning SLACK_MANAGER_USER_ID. If no response within a further 2 hours, escalate via email to the manager's address (sourced from HubSpot owner record). Log each reminder and timestamp. Do not auto-approve or auto-cancel.
Gmail — proposal send
OAuth refresh token expired or revoked (invalid_grant error)
Halt immediately. Alert support@gofullspec.com and SLACK_ALERT_CHANNEL_ID. Do not retry automatically as re-authorisation requires human action. Manual fallback: FullSpec team re-authorises the Gmail OAuth connection and updates GMAIL_OAUTH_REFRESH_TOKEN. Notify the rep so they can send the proposal manually in the interim.
HubSpot — deal stage update and activity log
PATCH or POST returns 4xx or 5xx after proposal has already been sent to the prospect
Retry the CRM write up to 3 times with 10-second intervals. If all retries fail, log the failed payload (deal ID, target stage, activity body) and alert SLACK_ALERT_CHANNEL_ID so the rep can manually update HubSpot. The proposal send is not rolled back; CRM accuracy is corrected manually.
Unhandled exceptions must never fail silently. Any execution path that does not match a defined success or failure branch must catch the exception, write a structured error log entry (including deal ID, step name, exception type, and timestamp), and fire an alert to SLACK_ALERT_CHANNEL_ID and support@gofullspec.com. A default catch-all handler must be the final node in both workflow graphs.
Integration and API SpecPage 3 of 3