Back to Pricing Approval Workflow

Integration and API Spec

The reference during the build: every tool connection, auth scope, field mapping, and error-handling rule.

4 pagesPDF · Technical
FS-DOC-05Technical

Integration and API Spec

Pricing Approval Workflow

[YourCompany.com] · Sales Department · Prepared by FullSpec · [Today's Date]

This document is the authoritative integration reference for the Pricing Approval Workflow build. It covers every tool connected to the automation, the exact authentication method and OAuth scopes required for each, field mappings between agent handoffs, the orchestration and credential-store layout, and defined error-handling behaviour for every integration point. The FullSpec team uses this specification to configure, test, and maintain all connections. No integration credentials should be hardcoded in workflow steps; all secrets live in the shared credential store as documented in Section 04.

01Tool inventory

Tool
Role in automation
Auth method
Min plan required
Used by agents
HubSpot
CRM trigger source and deal record write-back
OAuth 2.0 (private app token)
Sales Hub Starter or above (API access required)
Agent 1, Agent 3
Google Sheets
Structured approval audit log; inter-agent data handoff
OAuth 2.0 (service account JSON key)
Google Workspace or free Google account with Sheets API enabled
Agent 1, Agent 2
Slack
One-click approval messages to manager; rep outcome notifications
OAuth 2.0 (Slack app bot token)
Slack Free or above; interactive components require any paid plan for message actions in some workspaces
Agent 2, Agent 3
Gmail
Finance escalation emails when discount exceeds manager threshold
OAuth 2.0 (Google user account via OAuth consent)
Any Google Workspace account with Gmail API enabled
Agent 2
DocuSign
Quote delivery after approval decision is recorded (downstream, not orchestrated by agents)
OAuth 2.0 (JWT grant / Authorization Code grant)
DocuSign eSignature Standard or above
Not directly agent-controlled; referenced for context
Automation platform
Orchestration layer: hosts all three agent workflows, manages triggers, credential store, and retry logic
Internal platform credential store; no external auth
Plan supporting webhooks, scheduled polling, and multi-step branching
All agents (orchestration host)
Before you connect anything: configure every integration against a sandbox or test environment first. Use HubSpot sandbox accounts, a dedicated test Google Sheet, a Slack test workspace, and Gmail with a non-production sender address. Only promote credentials to production after all connections pass the Test and QA Plan checks. Never paste production credentials into a workflow step directly; all secrets must live in the credential store.

02Per-tool integration detail

HubSpot

Used by Agent 1 (Request Intake Agent) as the workflow trigger source, and by Agent 3 (CRM Update Agent) to write the final decision back to the deal record. A private app token is preferred over legacy API keys; the token is scoped to the minimum set of CRM objects required.

Auth method
OAuth 2.0 via HubSpot Private App. Generate a private app token in Settings > Integrations > Private Apps. Store token in the credential store as HUBSPOT_PRIVATE_APP_TOKEN. Do not use legacy API key.
Required scopes
crm.objects.deals.read | crm.objects.deals.write | crm.schemas.deals.read | crm.schemas.deals.write | crm.objects.contacts.read | crm.objects.owners.read
Trigger / webhook setup
Agent 1 uses a HubSpot webhook subscription on the 'Deal property change' event for the custom property pricing_exception_status. When this property is set to 'Requested', the webhook fires to the automation platform's inbound webhook URL. Configure via Settings > Integrations > Private Apps > Subscriptions. The platform must validate the X-HubSpot-Signature-v3 header using HMAC-SHA256 with HUBSPOT_CLIENT_SECRET on every inbound request before processing.
Required configuration
Custom deal properties must be created before build: pricing_exception_status (enumeration), requested_discount_pct (number), requested_price (number), exception_reason (single-line text), approval_status (enumeration: Pending / Approved / Denied), approved_price (number), approver_name (single-line text), approval_date (date), approval_conditions (multi-line text). Property internal names are stored in the credential store as HUBSPOT_DEAL_PROPERTY_MAP (JSON object). Pipeline stage IDs for the active sales pipeline are stored as HUBSPOT_PIPELINE_ID and HUBSPOT_STAGE_ID_APPROVAL_PENDING.
Rate limits
HubSpot private app: 150 requests per 10 seconds (burst); 1,000,000 daily. At 35 runs/month (~1.2 runs/day), throttling is not required. Agent 3 write-back averages 3 API calls per run; well within limits.
Constraints
HubSpot sandbox accounts do not receive live webhook events by default; use a test deal trigger during sandbox validation. Custom properties must be created on the deal object (not contact or company). Property internal names must match exactly what is stored in HUBSPOT_DEAL_PROPERTY_MAP.
// Trigger payload (inbound webhook from HubSpot)
X-HubSpot-Signature-v3: <hmac-sha256>
{
  "objectId": "<deal_id>",
  "propertyName": "pricing_exception_status",
  "propertyValue": "Requested",
  "changeSource": "CRM_UI",
  "eventId": "<uuid>"
}
// Agent 3 write-back (PATCH /crm/v3/objects/deals/{deal_id})
{
  "properties": {
    "approval_status": "Approved",
    "approved_price": "12500.00",
    "approver_name": "Renata Solano",
    "approval_date": "2025-07-05",
    "approval_conditions": "Valid for 30 days only"
  }
}
Google Sheets

Used by Agent 1 (Request Intake Agent) to write structured approval log entries, and read by Agent 2 (Approval Routing Agent) to pick up new rows for routing. Acts as the inter-agent handoff datastore. A service account with a JSON key is the recommended auth method to avoid user-session expiry interrupting scheduled polling.

Auth method
OAuth 2.0 via Google Cloud service account. Create a service account in Google Cloud Console, download the JSON key, and store the full JSON as GSHEETS_SERVICE_ACCOUNT_JSON in the credential store. Share the target spreadsheet with the service account email address (editor access).
Required scopes
https://www.googleapis.com/auth/spreadsheets | https://www.googleapis.com/auth/drive.file
Trigger / webhook setup
Agent 2 polls the approval log sheet on a 2-minute interval, filtering for rows where column J (routing_status) equals 'Pending'. No native webhook is available from Google Sheets; polling is the correct mechanism here. At 35 runs/month, polling frequency creates negligible API load.
Required configuration
A single spreadsheet is used as the approval log. Store its ID as GSHEETS_LOG_SPREADSHEET_ID and the sheet tab name as GSHEETS_LOG_SHEET_NAME (default: 'ApprovalLog'). The sheet must have the following column headers in row 1 (exact): log_id, deal_id, deal_name, rep_email, requested_discount_pct, requested_price, exception_reason, deal_value, discount_tier, routing_status, approver_slack_id, approver_email, decision, decision_timestamp, approver_name, conditions, crm_updated. Do not rename or reorder columns after build. The discount tier thresholds used to determine routing path are stored as DISCOUNT_TIER_THRESHOLDS (JSON: {"tier1_max_pct": 10, "tier2_max_pct": 20, "finance_threshold_pct": 20}).
Rate limits
Google Sheets API v4: 300 requests per minute per project; 60 requests per minute per user. At 35 runs/month and 2-minute polling intervals, peak usage is approximately 30 read requests per hour. No throttling required.
Constraints
The service account must have editor access to the spreadsheet; viewer access is insufficient for Agent 1 writes. Do not use Google Apps Script triggers as a substitute; all reads and writes must go through the Sheets API v4 from the automation platform. Column order is fixed; appending columns at the end is safe but inserting columns mid-sheet will break field mappings.
// Agent 1 append (POST spreadsheets/{id}/values/{range}:append)
{
  "range": "ApprovalLog!A:Q",
  "values": [[
    "LOG-0042", "12345678", "Acme Corp - Enterprise",
    "tom.rep@company.com", 18, 12500, "Competitive pressure",
    68000, "tier2", "Pending", "U04ABCD1234",
    "finance@company.com", "", "", "", "", "false"
  ]]
}
// Agent 2 poll read (GET spreadsheets/{id}/values/{range})
Filter: col J (routing_status) == 'Pending'
Slack

Used by Agent 2 (Approval Routing Agent) to send interactive one-click approval messages to the sales manager, and by Agent 3 (CRM Update Agent) to notify the sales rep of the final decision. A Slack app with bot token and interactive components enabled is required. The app must be installed by a workspace admin.

Auth method
OAuth 2.0 via Slack App bot token. Create a Slack app at api.slack.com/apps, enable Bot Token Scopes (see below), install to the workspace, and store the Bot User OAuth Token as SLACK_BOT_TOKEN. Store the app's Signing Secret as SLACK_SIGNING_SECRET for payload signature validation.
Required scopes
chat:write | chat:write.public | im:write | channels:read | users:read | users:read.email | reactions:write | incoming-webhook | commands (if slash commands used for threshold updates)
Webhook / interactive components setup
Enable Interactivity in the Slack app settings. Set the Interactivity Request URL to the automation platform's inbound Slack action endpoint. Every interactive payload (button click) includes a X-Slack-Signature header computed as HMAC-SHA256 of 'v0:' + timestamp + ':' + raw body, using SLACK_SIGNING_SECRET. The platform must validate this signature before processing any approval or denial action. Set up a separate Slack channel (e.g. #pricing-approvals) and store its channel ID as SLACK_APPROVAL_CHANNEL_ID. Store the finance approver's Slack user ID as SLACK_FINANCE_USER_ID and the general rep notification channel as SLACK_REP_NOTIFY_CHANNEL_ID.
Required configuration
Block Kit message templates for the approval request and outcome notification are stored in the credential/config store as SLACK_APPROVAL_MSG_TEMPLATE and SLACK_OUTCOME_MSG_TEMPLATE (JSON Block Kit payloads). Action IDs for the interactive buttons must be exactly: action_id: 'pricing_approve' and action_id: 'pricing_deny'. These are matched in the platform's Slack action handler.
Rate limits
Slack Web API: 1 message per second per channel (Tier 3: up to 50 requests per minute for chat.postMessage). At 35 runs/month with 2-3 Slack messages per run, peak is well below limits. No throttling required. If volume scales above 200 runs/month, implement a 1-second delay between successive postMessage calls.
Constraints
Interactive components (buttons) require a paid Slack plan in some enterprise workspace configurations; confirm with the workspace admin before build. The Slack app must be granted access to the DM scope to send direct messages to the approver. If the approver's Slack user ID cannot be resolved from their email, Agent 2 must fall back to posting to SLACK_APPROVAL_CHANNEL_ID with an @mention. Slack interactive payloads expire after 3 seconds; the platform must return an HTTP 200 immediately and process asynchronously.
// Agent 2 outbound approval message (chat.postMessage)
{
  "channel": "U04ABCD1234",
  "blocks": [
    { "type": "section", "text": { "type": "mrkdwn",
      "text": "*Pricing Exception Request*\nDeal: Acme Corp - Enterprise\nDiscount: 18% ($12,500)\nReason: Competitive pressure" } },
    { "type": "actions", "elements": [
      { "type": "button", "text": { "type": "plain_text", "text": "Approve" },
        "style": "primary", "action_id": "pricing_approve", "value": "LOG-0042" },
      { "type": "button", "text": { "type": "plain_text", "text": "Deny" },
        "style": "danger", "action_id": "pricing_deny", "value": "LOG-0042" }
    ]}
  ]
}
// Inbound interactive payload validation
X-Slack-Signature: v0=<hmac-sha256>
payload.actions[0].action_id -> 'pricing_approve' | 'pricing_deny'
payload.actions[0].value -> log_id (e.g. 'LOG-0042')
Integration and API SpecPage 1 of 3
FS-DOC-05Technical
Gmail

Used by Agent 2 (Approval Routing Agent) exclusively for finance escalation emails when the requested discount exceeds the manager-only threshold. Sends a structured email to the finance approver with deal context and an approval link. Auth is via OAuth 2.0 using a dedicated sender Google account.

Auth method
OAuth 2.0 via Google user account. Create OAuth credentials (Web Application type) in Google Cloud Console under the same project as Google Sheets. Store the refresh token as GMAIL_OAUTH_REFRESH_TOKEN, client ID as GMAIL_CLIENT_ID, and client secret as GMAIL_CLIENT_SECRET. Use a dedicated sender address (e.g. pricing-approvals@[YourCompany.com]) stored as GMAIL_SENDER_ADDRESS.
Required scopes
https://www.googleapis.com/auth/gmail.send | https://www.googleapis.com/auth/gmail.compose
Trigger / webhook setup
No inbound Gmail webhook is used. Agent 2 sends outbound only. Finance approver responses are captured via a tokenised approval link (hosted by the automation platform's HTTP endpoint) embedded in the email body, not via Gmail reply parsing. Store the base approval URL as APPROVAL_LINK_BASE_URL; the platform appends ?log_id={log_id}&token={hmac_token} to each link. Tokens are validated server-side using APPROVAL_LINK_SECRET.
Required configuration
Finance escalation email HTML template stored as GMAIL_ESCALATION_TEMPLATE_HTML in the config store. Template placeholders: {{deal_name}}, {{requested_discount_pct}}, {{requested_price}}, {{deal_value}}, {{exception_reason}}, {{rep_name}}, {{approve_link}}, {{deny_link}}. Finance approver email address stored as FINANCE_APPROVER_EMAIL. CC address (e.g. sales manager) stored as GMAIL_ESCALATION_CC.
Rate limits
Gmail API: 250 quota units per second (send = 100 units); 1,000,000,000 quota units per day. At maximum 35 finance escalations per month, usage is negligible. No throttling required.
Constraints
OAuth refresh tokens for Gmail expire if unused for 6 months or if the user revokes access. The platform must handle token refresh automatically using GMAIL_OAUTH_REFRESH_TOKEN before each send. If token refresh fails, Agent 2 must alert via SLACK_APPROVAL_CHANNEL_ID and halt the escalation step rather than silently dropping the email. The Gmail API does not support sending from an alias unless 'Send mail as' is configured in the Google account settings.
// Agent 2 outbound finance escalation (POST /gmail/v1/users/me/messages/send)
{
  "raw": "<base64url-encoded RFC 2822 message>",
  // Decoded headers:
  "From": "pricing-approvals@[YourCompany.com]",
  "To": "finance@[YourCompany.com]",
  "Cc": "sales.manager@[YourCompany.com]",
  "Subject": "Finance Approval Required: Acme Corp - 18% Discount",
  // Body includes approve_link and deny_link with HMAC tokens
  "approve_link": "https://hooks.platform.internal/approve?log_id=LOG-0042&token=<hmac>",
  "deny_link":    "https://hooks.platform.internal/deny?log_id=LOG-0042&token=<hmac>"
}
DocuSign

Used downstream after the CRM Update Agent records the final approval. DocuSign is not directly orchestrated by any of the three agents; it is referenced here for completeness and to document the integration credentials required if the automation is extended to trigger quote sending automatically in a future phase.

Auth method
OAuth 2.0 via JWT Grant (service integration) or Authorization Code Grant (user-facing). For automated sending, use JWT Grant. Store the RSA private key as DOCUSIGN_RSA_PRIVATE_KEY, integration key as DOCUSIGN_INTEGRATION_KEY, and user ID as DOCUSIGN_USER_ID. Account ID stored as DOCUSIGN_ACCOUNT_ID.
Required scopes
signature | impersonation (required for JWT grant acting on behalf of a user)
Webhook / trigger setup
Not configured in the current build. If extended: DocuSign Connect webhooks can fire on envelope status changes (e.g. 'completed') to a platform endpoint, enabling automatic CRM updates when a prospect signs. Store the webhook HMAC key as DOCUSIGN_CONNECT_HMAC_KEY if configured.
Required configuration
Template ID for the pricing exception quote template stored as DOCUSIGN_QUOTE_TEMPLATE_ID. Template roles and tab labels must match exactly: role 'Signer1' with tabs: 'ApprovedPrice', 'DealName', 'EffectiveDate'. Base URL stored as DOCUSIGN_BASE_URL (e.g. https://na4.docusign.net).
Rate limits
DocuSign Standard plan: 100 envelopes per month included; API rate limit 1,000 calls per hour. At 35 requests per month, well within limits. No throttling needed.
Constraints
JWT impersonation requires the DocuSign account admin to grant consent to the integration key before the first API call. Production and sandbox environments use different base URLs and separate account IDs; ensure DOCUSIGN_BASE_URL and DOCUSIGN_ACCOUNT_ID are environment-specific entries in the credential store.
// Future extension: POST /restapi/v2.1/accounts/{accountId}/envelopes
{
  "templateId": "<DOCUSIGN_QUOTE_TEMPLATE_ID>",
  "templateRoles": [{
    "roleName": "Signer1",
    "email": "prospect@client.com",
    "name": "Prospect Name",
    "tabs": {
      "textTabs": [
        { "tabLabel": "ApprovedPrice", "value": "12500" },
        { "tabLabel": "DealName", "value": "Acme Corp - Enterprise" }
      ]
    }
  }],
  "status": "sent"
}

03Field mappings between tools

The tables below document every field handoff between tools at each agent boundary. All field names in the Source field and Destination field columns are the exact internal property or column identifiers used in the automation platform. These names must match the credential store configuration and the Google Sheets column headers precisely.

Handoff 1: HubSpot to Google Sheets (Agent 1, Request Intake Agent)

Source tool
Source field
Destination tool
Destination field
HubSpot
hs_object_id
Google Sheets
deal_id
HubSpot
dealname
Google Sheets
deal_name
HubSpot
pricing_exception_status
Google Sheets
routing_status (set to 'Pending')
HubSpot
requested_discount_pct
Google Sheets
requested_discount_pct
HubSpot
requested_price
Google Sheets
requested_price
HubSpot
exception_reason
Google Sheets
exception_reason
HubSpot
amount
Google Sheets
deal_value
HubSpot
hubspot_owner_id (resolved to email)
Google Sheets
rep_email
Automation platform (computed)
tier classification from DISCOUNT_TIER_THRESHOLDS
Google Sheets
discount_tier
Automation platform (computed)
approver Slack user ID lookup by rep_email
Google Sheets
approver_slack_id
Automation platform (computed)
FINANCE_APPROVER_EMAIL constant
Google Sheets
approver_email
Automation platform (generated)
UUID log identifier
Google Sheets
log_id

Handoff 2: Google Sheets to Slack / Gmail (Agent 2, Approval Routing Agent)

Source tool
Source field
Destination tool
Destination field
Google Sheets
log_id
Slack
action button value (payload.actions[0].value)
Google Sheets
deal_name
Slack
Block Kit section text {{deal_name}}
Google Sheets
requested_discount_pct
Slack
Block Kit section text {{discount_pct}}
Google Sheets
requested_price
Slack
Block Kit section text {{requested_price}}
Google Sheets
exception_reason
Slack
Block Kit section text {{exception_reason}}
Google Sheets
approver_slack_id
Slack
chat.postMessage channel parameter
Google Sheets
log_id
Gmail
approve_link and deny_link query parameter log_id
Google Sheets
deal_name
Gmail
email subject and body {{deal_name}}
Google Sheets
requested_discount_pct
Gmail
email body {{requested_discount_pct}}
Google Sheets
requested_price
Gmail
email body {{requested_price}}
Google Sheets
deal_value
Gmail
email body {{deal_value}}
Google Sheets
exception_reason
Gmail
email body {{exception_reason}}
Google Sheets
rep_email (resolved to name)
Gmail
email body {{rep_name}}
Google Sheets
approver_email
Gmail
To header

Handoff 3: Google Sheets to HubSpot and Slack (Agent 3, CRM Update Agent)

Source tool
Source field
Destination tool
Destination field
Google Sheets
deal_id
HubSpot
hs_object_id (deal lookup key)
Google Sheets
decision
HubSpot
approval_status
Google Sheets
approved_price (if Approved)
HubSpot
approved_price
Google Sheets
approver_name
HubSpot
approver_name
Google Sheets
decision_timestamp
HubSpot
approval_date
Google Sheets
conditions
HubSpot
approval_conditions
Google Sheets
deal_name
Slack
rep notification message {{deal_name}}
Google Sheets
decision
Slack
rep notification message {{decision}}
Google Sheets
approved_price
Slack
rep notification message {{approved_price}}
Google Sheets
conditions
Slack
rep notification message {{conditions}}
Google Sheets
rep_email (resolved to Slack user ID)
Slack
chat.postMessage channel (DM to rep)
Integration and API SpecPage 2 of 3
FS-DOC-05Technical

04Build stack and orchestration

Orchestration layout
Three separate workflow definitions, one per agent: 'Agent1_RequestIntake', 'Agent2_ApprovalRouting', 'Agent3_CRMUpdate'. All three share a single credential store namespace. No credentials are duplicated across workflows; each workflow references the shared store by key name.
Agent 1 trigger mechanism
Inbound webhook from HubSpot (deal property change event on pricing_exception_status = 'Requested'). The platform exposes a unique HTTPS endpoint for this workflow. HubSpot webhook signature (X-HubSpot-Signature-v3, HMAC-SHA256) is validated on receipt before any processing step executes. If signature validation fails, return HTTP 401 and log the rejected payload.
Agent 2 trigger mechanism
Scheduled poll of the Google Sheets approval log every 2 minutes, filtering for rows where routing_status = 'Pending'. On match, the workflow executes the routing logic. The platform also exposes an inbound HTTP endpoint for Slack interactive component payloads (button clicks) and for Gmail approval link callbacks; both endpoints validate their respective signatures (SLACK_SIGNING_SECRET and APPROVAL_LINK_SECRET) before processing.
Agent 3 trigger mechanism
Scheduled poll of the Google Sheets approval log every 2 minutes, filtering for rows where decision is not empty AND crm_updated = 'false'. On match, Agent 3 performs the HubSpot write-back and Slack rep notification, then sets crm_updated = 'true' to prevent duplicate processing.
Shared credential store
All secrets and configuration constants are stored in the platform's encrypted credential store. Workflow steps reference keys by name only. Rotating a credential requires updating the store entry; no workflow step code changes are needed.
Environment separation
Maintain two credential store namespaces: 'pricing-approval-sandbox' and 'pricing-approval-prod'. Sandbox entries point to HubSpot sandbox account, test Google Sheet, Slack test workspace, and Gmail test sender. Promote to prod namespace only after QA sign-off.
All values shown are placeholders. Replace with actual credentials in the encrypted store before testing.
// Credential store contents (pricing-approval-prod namespace)

// HubSpot
HUBSPOT_PRIVATE_APP_TOKEN        = "pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
HUBSPOT_CLIENT_SECRET            = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
HUBSPOT_PIPELINE_ID              = "default"
HUBSPOT_STAGE_ID_APPROVAL_PENDING = "12345678"
HUBSPOT_DEAL_PROPERTY_MAP        = "{\"requested_discount_pct\":\"requested_discount_pct\", ...}"

// Google Sheets
GSHEETS_SERVICE_ACCOUNT_JSON     = "{...full service account JSON key...}"
GSHEETS_LOG_SPREADSHEET_ID       = "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms"
GSHEETS_LOG_SHEET_NAME           = "ApprovalLog"

// Slack
SLACK_BOT_TOKEN                  = "xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"
SLACK_SIGNING_SECRET             = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
SLACK_APPROVAL_CHANNEL_ID        = "C04XXXXXXXX"
SLACK_REP_NOTIFY_CHANNEL_ID      = "C04YYYYYYYYY"
SLACK_FINANCE_USER_ID            = "U04ZZZZZZZZ"
SLACK_APPROVAL_MSG_TEMPLATE      = "{...Block Kit JSON...}"
SLACK_OUTCOME_MSG_TEMPLATE       = "{...Block Kit JSON...}"

// Gmail
GMAIL_CLIENT_ID                  = "xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com"
GMAIL_CLIENT_SECRET              = "GOCSPX-xxxxxxxxxxxxxxxxxxxxxxxx"
GMAIL_OAUTH_REFRESH_TOKEN        = "1//xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
GMAIL_SENDER_ADDRESS             = "pricing-approvals@[YourCompany.com]"
GMAIL_ESCALATION_TEMPLATE_HTML   = "<path-or-inline-HTML-template>"
GMAIL_ESCALATION_CC              = "sales.manager@[YourCompany.com]"
FINANCE_APPROVER_EMAIL           = "finance@[YourCompany.com]"

// Approval link (Gmail callback)
APPROVAL_LINK_BASE_URL           = "https://hooks.platform.internal"
APPROVAL_LINK_SECRET             = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

// DocuSign (future extension)
DOCUSIGN_INTEGRATION_KEY         = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
DOCUSIGN_RSA_PRIVATE_KEY         = "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----"
DOCUSIGN_USER_ID                 = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
DOCUSIGN_ACCOUNT_ID              = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
DOCUSIGN_BASE_URL                = "https://na4.docusign.net"
DOCUSIGN_QUOTE_TEMPLATE_ID       = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

// Discount tier thresholds
DISCOUNT_TIER_THRESHOLDS         = "{\"tier1_max_pct\": 10, \"tier2_max_pct\": 20, \"finance_threshold_pct\": 20}"
Never hardcode any credential, token, or threshold value inside a workflow step, conditional branch, or code node. Every value listed above must be read from the credential store at runtime. If a key is missing or empty, the workflow step must throw a named error ('MISSING_CREDENTIAL') and halt execution, not proceed with a null or default value.

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 platform's execution log with the log_id, the failing step name, the HTTP status or exception message, and a UTC timestamp. Where a fallback action is defined, it must execute before the workflow halts.

Integration
Scenario
Required behaviour
HubSpot (inbound webhook)
X-HubSpot-Signature-v3 validation fails
Return HTTP 401 immediately. Log rejected payload with source IP. Do not process. Alert via SLACK_APPROVAL_CHANNEL_ID. No retry.
HubSpot (deal field read, Agent 1)
Required deal property is null or missing (e.g. requested_discount_pct empty)
Halt intake. Write row to Google Sheets with routing_status = 'ValidationFailed' and error_detail noting the missing field. Send Slack DM to rep_email asking them to complete the request. No retry until rep resubmits.
HubSpot (PATCH deal, Agent 3)
API returns 4xx (e.g. 404 deal not found, 403 scope error)
Retry 0 times for 4xx (not retriable). Log error with HTTP status. Post alert to SLACK_APPROVAL_CHANNEL_ID with deal_id and error detail. Mark crm_updated = 'error' in sheet. Manual resolution required.
HubSpot (PATCH deal, Agent 3)
API returns 5xx or network timeout
Retry up to 3 times with exponential backoff: 30s, 60s, 120s. If all retries fail, post alert to SLACK_APPROVAL_CHANNEL_ID, mark crm_updated = 'error', and log full error. Do not notify rep until CRM write succeeds.
Google Sheets (append, Agent 1)
API returns 5xx or quota exceeded (429)
Retry up to 3 times with backoff: 10s, 20s, 40s. If all retries fail, alert via SLACK_APPROVAL_CHANNEL_ID with deal_id and raw HubSpot payload so the row can be inserted manually. Do not proceed to routing until the log entry exists.
Google Sheets (poll read, Agent 2)
Spreadsheet ID invalid or service account access revoked
Halt the polling workflow. Post critical alert to SLACK_APPROVAL_CHANNEL_ID: 'Approval log unreachable, manual intervention required'. Retry every 5 minutes up to 3 times. If unresolved, escalate to support@gofullspec.com.
Slack (chat.postMessage, Agent 2)
Approver Slack user ID not found or bot not in channel
Fall back to posting the approval message to SLACK_APPROVAL_CHANNEL_ID with an @mention of the approver by display name. Log the fallback action. Do not halt the workflow. If SLACK_APPROVAL_CHANNEL_ID also fails, send Gmail escalation regardless of discount tier.
Slack (interactive component callback, Agent 2)
Callback payload signature invalid (SLACK_SIGNING_SECRET mismatch)
Return HTTP 401. Log the rejected payload. Do not record any decision. Post alert to SLACK_APPROVAL_CHANNEL_ID. No retry; the approver must click the button again.
Slack (interactive component callback, Agent 2)
Callback received after reminder window expired (stale action)
Check log_id status in Google Sheets. If routing_status is already 'Decided', return HTTP 200 and post ephemeral message to the approver: 'This request has already been decided.' Do not write a duplicate decision.
Gmail (send escalation, Agent 2)
OAuth refresh token expired or revoked
Halt the Gmail send step. Post alert to SLACK_APPROVAL_CHANNEL_ID: 'Finance escalation email failed, Gmail auth error. Manual escalation required.' Log the error with log_id. Do not silently skip the escalation. Requires manual token refresh in credential store.
Gmail (approval link callback, Agent 2)
HMAC token invalid or log_id not found
Return HTTP 400 to the approver's browser with a human-readable message: 'This link is invalid or has expired. Please contact your sales manager.' Log the attempt. Do not record any decision.
Automation platform (all agents)
Unhandled exception in any workflow step (uncaught error type)
The platform must catch all unhandled exceptions via a global error handler. Write the exception type, step name, log_id (if available), and stack trace to the execution log. Post an alert to SLACK_APPROVAL_CHANNEL_ID. Mark the affected Google Sheets row with routing_status or crm_updated = 'error'. Never fail silently.
Reminder cadence for unanswered approval requests: Agent 2 must send a reminder Slack message or Gmail (matching the original channel) after 4 business hours of no response. A second reminder fires at 8 business hours. After 24 business hours with no decision, the request is automatically escalated to the next tier approver and the log row is updated with routing_status = 'Escalated'. All reminder intervals are configurable via the credential store key REMINDER_INTERVAL_HOURS (default: '4,8,24').
Integration and API SpecPage 3 of 3

More documents for this process

Every document generated for Pricing Approval Workflow.

Launch Plan
Operations · Owner
View
ROI and Business Case
Finance · Owner
View
Process Runbook / SOP
Operations · Owner
View
Developer Handover Pack
Technical · Developer
View
Test and QA Plan
Quality · Developer
View