Back to Referral Programme Management

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

Referral Programme Management

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

This document defines every integration point, authentication method, required scope, field mapping, and error-handling behaviour for the Referral Programme Management automation. It is written for the FullSpec build team and covers the six production tools (Typeform, HubSpot, Google Sheets, Gmail, Slack, and Xero) plus the orchestration layer that connects them. All credential references, rate limits, and retry rules in this document are mandatory implementation requirements, not suggestions. Nothing in this document requires action from the process owner; the FullSpec team owns the full build, configuration, and validation of every integration described here.

01Tool inventory

Tool
Role in automation
Auth method
Min plan required
Used by agents
Orchestration layer
Hosts all three agent workflows, manages credential store, handles retries and routing
Service account / environment variables
Self-hosted or cloud plan with webhook inbound support
All agents
Typeform
Inbound trigger: referral form submission fires webhook to Agent 1
OAuth 2.0 (webhook HMAC-SHA256 signature validation)
Basic ($29/month) or above
Agent 1 (Referral Intake)
HubSpot
Duplicate contact check, contact creation, deal creation, deal stage change events
Private App token (Bearer)
Starter CRM ($50/month) or above
Agent 1 (Referral Intake), Agent 3 (Reward Processing)
Google Sheets
Programme register: row append on intake, row update on stage change and reward
OAuth 2.0 (service account JSON key)
Free (Google Workspace account sufficient)
Agent 1 (Referral Intake), Agent 3 (Reward Processing)
Gmail
Outbound email: acknowledgement to referrer, reward notification to referrer
OAuth 2.0 (delegated user credentials)
Free Gmail or Google Workspace
Agent 2 (Referral Communications)
Slack
Outbound alert: lead notification posted to sales rep channel on intake
Bot token (xoxb-) via Slack App OAuth install
Free Slack plan sufficient
Agent 2 (Referral Communications)
Xero
Draft bill creation for reward amount on Closed Won deal event
OAuth 2.0 (authorization code flow, offline access)
Xero Starter ($35/month) or above
Agent 3 (Reward Processing)
Before you connect anything: create a sandbox or test environment for every tool that offers one (Typeform preview, HubSpot sandbox account, Xero demo company, a staging Google Sheet). Validate the full end-to-end flow against test credentials before any production credential is entered into the credential store. Production credentials must never be used during development or QA.
Integration and API SpecPage 1 of 5
FS-DOC-05Technical

02Per-tool integration detail

Typeform

Typeform acts as the entry-point trigger for the entire automation. The webhook fires immediately on form submission and carries all referral field values in the response payload. Signature validation is mandatory before any downstream action is taken.

Auth method
OAuth 2.0 for API access. Webhook payloads are authenticated via HMAC-SHA256 signature: the Typeform-generated secret is stored in the credential store and compared against the X-Typeform-Signature header on every inbound request. Requests that fail signature validation must be rejected with HTTP 401 and logged.
Required scopes
forms:read responses:read webhooks:write webhooks:read
Webhook / trigger setup
Create a webhook on the target form via Typeform Connect > Webhooks. Set the endpoint URL to the orchestration layer's inbound webhook receiver. Enable secret signing. Select all response events. The webhook secret must be rotated and updated in the credential store if the Typeform form is republished or cloned.
Required configuration
Store the Typeform Form ID in the credential store (not hardcoded). Map field references (field_id values, not display labels) for: referrer_name, referrer_email, referred_contact_name, referred_contact_email, referred_contact_company, referral_notes. Field reference IDs must be retrieved from the form schema via GET /forms/{formId} and stored at setup time.
Rate limits
Typeform API: 200 requests/minute per token. Webhook delivery: no inbound rate limit applies on the receiver side. At ~40 referrals/month (roughly 1-2 per day) no throttling is required. Burst scenarios (e.g. a campaign generating 20+ submissions in an hour) remain well within limits.
Constraints
Duplicate webhook delivery is possible if Typeform retries on a failed acknowledgement. The receiver must return HTTP 200 within 10 seconds. Idempotency must be implemented using the response token (token field in payload) as a deduplication key checked against the credential store or a short-lived cache.
// Inbound webhook payload (key fields)
event_id: string          // unique per delivery attempt
token: string             // unique per form response, use as idempotency key
form_response.answers[]   // array of field objects
  field.ref: string       // use this, not field title
  text | email: string    // answer value by type

// Fields extracted for downstream agents
referrer_name:              answers[ref=referrer_name].text
referrer_email:             answers[ref=referrer_email].email
referred_contact_name:      answers[ref=referred_contact_name].text
referred_contact_email:     answers[ref=referred_contact_email].email
referred_contact_company:   answers[ref=referred_contact_company].text
referral_notes:             answers[ref=referral_notes].text
HubSpot

HubSpot is used by two agents: Agent 1 queries and writes contact and deal records on intake; Agent 3 listens for deal stage change events via webhook subscription to sync the register and trigger reward processing.

Auth method
Private App token (Bearer token). Create a dedicated Private App in HubSpot Settings > Integrations > Private Apps. Store the token as HUBSPOT_PRIVATE_APP_TOKEN in the credential store. Do not use legacy API keys.
Required scopes
crm.objects.contacts.read crm.objects.contacts.write crm.objects.deals.read crm.objects.deals.write crm.schemas.contacts.read crm.schemas.deals.read timeline webhook
Webhook / trigger setup
Create a Webhook Subscription via HubSpot Settings > Integrations > Webhooks for event type deal.propertyChange on property dealstage. Scope to deals with the custom property referral_source set (non-null). The subscription endpoint is the orchestration layer's HubSpot event receiver. HubSpot signs payloads with a SHA-256 HMAC using the app client secret; validate the X-HubSpot-Signature-V3 header on every event. Also subscribe to deal.creation to confirm new deal records created by Agent 1.
Required configuration
Custom contact properties to create in HubSpot before go-live: referrer_name (single-line text), referrer_email (email), reward_tier (dropdown: Tier 1, Tier 2, Tier 3), referral_submission_date (date). Custom deal properties: referral_source (single-line text), reward_amount (number), referral_register_row_id (number, used to match Google Sheets row). Pipeline ID and stage IDs for the sales pipeline must be retrieved via GET /crm/v3/pipelines/deals and stored in the credential store. The Closed Won stage ID must be stored separately as HUBSPOT_CLOSED_WON_STAGE_ID.
Rate limits
Private App tokens: 100 requests/10 seconds, 250,000 requests/day. At 40 referrals/month the daily limit is never approached. Each intake creates approximately 4-6 API calls (search, create contact, create deal, update properties). No throttling required at current volume; implement exponential backoff for 429 responses regardless.
Constraints
Contact search uses POST /crm/v3/objects/contacts/search filtering on email (exact match). If no email is supplied in the Typeform submission, the duplicate check cannot run and the record must be routed to a manual review Slack alert. HubSpot webhook events may arrive out of order; the orchestration layer must process events idempotently using the objectId and occurredAt fields.
// Contact search request
POST /crm/v3/objects/contacts/search
{ filterGroups: [{ filters: [{ propertyName: 'email', operator: 'EQ', value: referred_contact_email }] }] }

// Contact create request
POST /crm/v3/objects/contacts
{ properties: { firstname, lastname, email, referrer_name, referrer_email, reward_tier, referral_submission_date } }

// Deal create request
POST /crm/v3/objects/deals
{ properties: { dealname, pipeline, dealstage, referral_source, reward_amount, referral_register_row_id } }

// Stage change webhook event (inbound)
objectId: number           // HubSpot deal ID
propertyName: string       // 'dealstage'
propertyValue: string      // new stage ID
occurredAt: ISO8601        // event timestamp
Google Sheets

Google Sheets is the programme register. Agent 1 appends a new row on intake; Agent 3 updates the matching row on every deal stage change and again when the reward bill is created in Xero. The sheet structure is fixed and must not be altered after go-live without updating the field mappings in the orchestration layer.

Auth method
OAuth 2.0 via a Google service account. Generate a service account in Google Cloud Console, download the JSON key, and store the full JSON as GOOGLE_SERVICE_ACCOUNT_JSON in the credential store. Share the target spreadsheet with the service account email address (editor permission).
Required scopes
https://www.googleapis.com/auth/spreadsheets
Webhook / trigger setup
Google Sheets does not support outbound webhooks natively. Agent 3 is triggered by HubSpot deal stage events, not by Sheets. No inbound trigger setup is required for this tool.
Required configuration
Store the Spreadsheet ID as GSHEETS_SPREADSHEET_ID in the credential store. Store the register tab name as GSHEETS_REGISTER_TAB (default: Referral Register). Column structure must match exactly (see field mappings, section 03). The header row must be row 1. Data rows start at row 2. The referral_register_row_id stored on the HubSpot deal is the integer row number in this sheet, used for targeted updates via PATCH on the exact range (e.g. A7:L7).
Rate limits
Google Sheets API v4: 300 read requests/minute per project, 300 write requests/minute per project. At 40 referrals/month with up to 5 update events per referral, peak write load is under 10 requests/hour. No throttling required. Implement retry with backoff on 429 and 503 responses.
Constraints
Row-level updates require the exact row number, which must be persisted on the HubSpot deal record at creation time. If the row number is missing from the deal, the update step must log an error and alert via Slack rather than appending a duplicate row. Formula cells must not be overwritten; columns containing formulas should be excluded from the write range.
// Append new row on intake
POST /v4/spreadsheets/{spreadsheetId}/values/{range}:append
range: 'Referral Register!A:L'
valueInputOption: RAW
values: [[submission_date, referrer_name, referrer_email, referred_name, referred_email,
          referred_company, reward_tier, reward_amount, deal_stage, hubspot_deal_id,
          status, notes]]

// Update existing row on stage change
PUT /v4/spreadsheets/{spreadsheetId}/values/'Referral Register!I{rowNum}:L{rowNum}'
values: [[new_deal_stage, hubspot_deal_id, new_status, updated_notes]]
Integration and API SpecPage 2 of 5
FS-DOC-05Technical
Gmail

Gmail sends two categories of email managed by Agent 2: (1) an acknowledgement email sent to the referrer immediately after a new referral is logged, and (2) a reward notification email sent to the referrer when the deal reaches Closed Won. Both emails use stored templates with named placeholders substituted at runtime.

Auth method
OAuth 2.0 delegated credentials for a designated sending account (e.g. referrals@[YourCompany.com]). Store the refresh token as GMAIL_REFRESH_TOKEN and the client credentials as GMAIL_CLIENT_ID and GMAIL_CLIENT_SECRET in the credential store. Token refresh is handled automatically by the orchestration layer using the offline scope.
Required scopes
https://www.googleapis.com/auth/gmail.send https://www.googleapis.com/auth/gmail.compose
Webhook / trigger setup
Gmail is outbound only in this automation. No inbound Gmail trigger or push notification subscription is used. Agent 2 is triggered by upstream events (deal creation from Agent 1; Closed Won event from HubSpot via Agent 3).
Required configuration
Two email templates must be created and stored in the credential store as plain-text strings with named placeholders: GMAIL_TEMPLATE_ACKNOWLEDGEMENT and GMAIL_TEMPLATE_REWARD_NOTIFICATION. Placeholders: {{referrer_name}}, {{referred_contact_name}}, {{reward_tier}}, {{reward_amount}}, {{expected_payment_date}}, {{company_name}}. The From address and display name are stored as GMAIL_SENDER_ADDRESS and GMAIL_SENDER_NAME. Reply-to address stored as GMAIL_REPLY_TO.
Rate limits
Gmail API: 250 quota units/second per user, 1,000,000,000 quota units/day. Sending a message costs 100 units. At 2 emails per referral and 40 referrals/month (80 emails/month), limits are not a concern. No throttling required.
Constraints
If a referrer email address is missing or malformed, the send step must not attempt delivery. Agent 2 must validate the email format before calling the API and route invalid cases to a Slack alert for manual follow-up. Sent message IDs should be logged against the HubSpot deal record for audit purposes.
// Send message request
POST /gmail/v1/users/me/messages/send
{ raw: base64url(RFC2822 formatted message) }

// RFC2822 headers
From: {{GMAIL_SENDER_NAME}} <{{GMAIL_SENDER_ADDRESS}}>
To: referrer_email
Reply-To: {{GMAIL_REPLY_TO}}
Subject: [Acknowledgement] Your referral of {{referred_contact_name}} has been received
Content-Type: text/plain; charset=utf-8

// Template substitution at runtime
body = GMAIL_TEMPLATE_ACKNOWLEDGEMENT
       .replace('{{referrer_name}}', referrer_name)
       .replace('{{referred_contact_name}}', referred_contact_name)
       .replace('{{reward_tier}}', reward_tier)
Slack

Slack is used by Agent 2 to post a formatted lead alert to the designated sales rep channel immediately after a new referral is logged and the HubSpot deal is created. A second alert is posted to a separate channel (or the same channel) if a duplicate contact is detected, flagging the case for manager review.

Auth method
Bot token (xoxb- prefix) generated during Slack App OAuth install. Create a Slack App in the Slack API console, grant the required bot scopes, and install it to the workspace. Store the bot token as SLACK_BOT_TOKEN in the credential store.
Required scopes
chat:write chat:write.public channels:read users:read
Webhook / trigger setup
Slack is outbound only in this automation. Messages are posted via the chat.postMessage API method. No Event Subscriptions or incoming webhooks are required. The target channel IDs (not names) must be stored in the credential store: SLACK_SALES_CHANNEL_ID (lead alert channel) and SLACK_EXCEPTION_CHANNEL_ID (duplicate and error alerts).
Required configuration
Store SLACK_SALES_CHANNEL_ID and SLACK_EXCEPTION_CHANNEL_ID in the credential store. Message payloads use Slack Block Kit JSON. Two block templates must be defined: SLACK_BLOCK_LEAD_ALERT (new referral) and SLACK_BLOCK_DUPLICATE_ALERT (duplicate detected). The HubSpot deal URL pattern is stored as HUBSPOT_DEAL_URL_PATTERN (e.g. https://app.hubspot.com/contacts/{portalId}/deal/{dealId}).
Rate limits
Slack Web API: Tier 3 methods (chat.postMessage): 50 requests/minute per token. At 40 referrals/month the rate limit is never approached. No throttling required. Retry on 429 with the Retry-After header value.
Constraints
The bot must be added to both target channels before go-live. Posting to a private channel where the bot is not a member returns a not_in_channel error; this must be caught and logged. Channel IDs must be resolved at setup time and stored; do not resolve channel names at runtime.
// chat.postMessage request
POST https://slack.com/api/chat.postMessage
Authorization: Bearer {{SLACK_BOT_TOKEN}}
{
  channel: SLACK_SALES_CHANNEL_ID,
  blocks: [
    { type: 'header', text: { type: 'plain_text', text: 'New Referral Received' } },
    { type: 'section', fields: [
        { type: 'mrkdwn', text: '*Referrer:* {{referrer_name}}' },
        { type: 'mrkdwn', text: '*Referred:* {{referred_contact_name}} ({{referred_contact_company}})' },
        { type: 'mrkdwn', text: '*Reward Tier:* {{reward_tier}}' },
        { type: 'mrkdwn', text: '*Deal:* <{{hubspot_deal_url}}|Open in HubSpot>' }
    ]}
  ]
}
Xero

Xero is used by Agent 3 to create a draft bill for the referral reward amount when a referred deal reaches Closed Won in HubSpot. The bill is created in draft state so the finance contact retains full approval and payment authority. No automated payment or approval action is taken.

Auth method
OAuth 2.0 authorization code flow with offline access (refresh token). Register the automation as a Xero App in Xero Developer Portal. Store credentials as XERO_CLIENT_ID, XERO_CLIENT_SECRET, XERO_REFRESH_TOKEN, and XERO_TENANT_ID in the credential store. The access token must be refreshed automatically before each API call using the stored refresh token.
Required scopes
accounting.transactions accounting.contacts.read offline_access
Webhook / trigger setup
Xero is outbound only in this automation. The bill creation is triggered by the HubSpot Closed Won event processed by Agent 3, not by a Xero event. No Xero webhook subscription is required.
Required configuration
Store the target Xero account code for referral rewards as XERO_REWARD_ACCOUNT_CODE (e.g. 420 for marketing expense; confirm with finance contact). Store XERO_TENANT_ID. The referrer must exist as a Contact in Xero before bill creation; store a lookup map of referrer_email to Xero Contact ID in the credential store as XERO_CONTACT_MAP (JSON object). If a referrer is not found in the map, the bill creation step must alert via Slack and halt rather than create an unlinked bill. Reward amounts per tier must be stored as XERO_REWARD_TIER_1_AMOUNT, XERO_REWARD_TIER_2_AMOUNT, XERO_REWARD_TIER_3_AMOUNT.
Rate limits
Xero API: 60 requests/minute per connection, 5,000 requests/day. At 40 referrals/month the daily limit is never approached. No throttling required. Implement exponential backoff on 429 responses (Xero uses 429 with a Retry-After header on minute-level rate limit breach).
Constraints
Bills must be created with Status: DRAFT only. Never set Status: AUTHORISED or SUBMITTED in the automated call. The LineItem Description must include the referrer name, referred contact name, and deal close date for audit traceability. The invoice number (InvoiceNumber field) must follow the pattern REF-{hubspot_deal_id} to allow cross-referencing.
// Create bill (Account Payable invoice)
POST https://api.xero.com/api.xro/2.0/Invoices
Xero-Tenant-Id: {{XERO_TENANT_ID}}
{
  Type: 'ACCPAY',
  Status: 'DRAFT',
  InvoiceNumber: 'REF-{{hubspot_deal_id}}',
  Contact: { ContactID: '{{xero_contact_id}}' },
  Date: '{{deal_close_date}}',
  DueDate: '{{deal_close_date + 30 days}}',
  LineItems: [{
    Description: 'Referral reward: {{referrer_name}} referred {{referred_contact_name}}, closed {{deal_close_date}}',
    Quantity: 1,
    UnitAmount: {{reward_amount}},
    AccountCode: '{{XERO_REWARD_ACCOUNT_CODE}}'
  }]
}
Integration and API SpecPage 3 of 5
FS-DOC-05Technical

03Field mappings between tools

The tables below define every field handoff between tools at each agent boundary. Use the exact field names shown (monospace) when configuring the orchestration layer. Do not substitute display labels for API field names.

Agent 1 handoff: Typeform to HubSpot (contact and deal creation)

Source tool
Source field
Destination tool
Destination field
Typeform
answers[ref=referrer_name].text
HubSpot Contact
referrer_name
Typeform
answers[ref=referrer_email].email
HubSpot Contact
referrer_email
Typeform
answers[ref=referred_contact_name].text
HubSpot Contact
firstname + lastname (split on first space)
Typeform
answers[ref=referred_contact_email].email
HubSpot Contact
email
Typeform
answers[ref=referred_contact_company].text
HubSpot Contact
company
Typeform
answers[ref=referral_notes].text
HubSpot Deal
description
Typeform
form_response.submitted_at (ISO8601)
HubSpot Contact
referral_submission_date
Typeform
token (string)
Orchestration cache
idempotency_key (dedup check only, not stored in HubSpot)
Programme rules config
reward_tier (resolved from referrer_email lookup)
HubSpot Contact
reward_tier
Programme rules config
reward_amount (resolved from tier)
HubSpot Deal
reward_amount

Agent 1 handoff: HubSpot to Google Sheets (register row append)

Source tool
Source field
Destination tool
Destination field
HubSpot Deal
properties.createdate (ISO8601, formatted YYYY-MM-DD)
Google Sheets
Column A: submission_date
HubSpot Contact
properties.referrer_name
Google Sheets
Column B: referrer_name
HubSpot Contact
properties.referrer_email
Google Sheets
Column C: referrer_email
HubSpot Contact
properties.firstname + lastname
Google Sheets
Column D: referred_contact_name
HubSpot Contact
properties.email
Google Sheets
Column E: referred_contact_email
HubSpot Contact
properties.company
Google Sheets
Column F: referred_contact_company
HubSpot Contact
properties.reward_tier
Google Sheets
Column G: reward_tier
HubSpot Deal
properties.reward_amount
Google Sheets
Column H: reward_amount
HubSpot Deal
properties.dealstage (label resolved via pipeline API)
Google Sheets
Column I: deal_stage
HubSpot Deal
id (integer)
Google Sheets
Column J: hubspot_deal_id
Hardcoded constant
'Open'
Google Sheets
Column K: status
Typeform
answers[ref=referral_notes].text
Google Sheets
Column L: notes

Agent 3 handoff: HubSpot stage change event to Google Sheets (row update)

Source tool
Source field
Destination tool
Destination field
HubSpot Webhook
propertyValue (stage ID, resolved to label)
Google Sheets
Column I: deal_stage (row {referral_register_row_id})
HubSpot Webhook
objectId (deal ID)
Google Sheets
Column J: hubspot_deal_id (verify match before update)
Derived
If propertyValue == HUBSPOT_CLOSED_WON_STAGE_ID then 'Closed Won' else 'In Progress'
Google Sheets
Column K: status
HubSpot Webhook
occurredAt (ISO8601, formatted YYYY-MM-DD HH:MM)
Google Sheets
Column L: notes (append 'Stage updated: {timestamp}')

Agent 3 handoff: HubSpot Closed Won event to Xero (bill creation)

Source tool
Source field
Destination tool
Destination field
HubSpot Deal
properties.reward_amount
Xero Invoice
LineItems[0].UnitAmount
HubSpot Contact
properties.referrer_email (lookup in XERO_CONTACT_MAP)
Xero Invoice
Contact.ContactID
HubSpot Deal
id (integer)
Xero Invoice
InvoiceNumber (prefix REF-)
HubSpot Deal
properties.closedate (ISO8601, formatted YYYY-MM-DD)
Xero Invoice
Date
Derived
closedate + 30 calendar days
Xero Invoice
DueDate
HubSpot Contact
properties.referrer_name
Xero Invoice
LineItems[0].Description (part of string)
HubSpot Contact
properties.firstname + lastname (referred)
Xero Invoice
LineItems[0].Description (part of string)
Credential store
XERO_REWARD_ACCOUNT_CODE
Xero Invoice
LineItems[0].AccountCode

04Build stack and orchestration

Orchestration layout
Three discrete workflows, one per agent, hosted in the workflow automation platform. Each workflow is independently deployable and has its own inbound trigger, error handler, and logging sink. Workflows share a single centrally managed credential store; no credentials are hardcoded in any workflow step.
Shared credential store
All API keys, OAuth tokens, template strings, configuration IDs, and tier amounts are held in the platform's encrypted credential store and referenced by name. The credential store is the single source of truth for all environment-specific values. Local overrides are not permitted.
Agent 1 trigger mechanism
Webhook (push). Typeform fires a POST request to the orchestration layer's inbound webhook receiver URL on each form submission. The receiver validates the HMAC-SHA256 signature using TYPEFORM_WEBHOOK_SECRET before passing the payload to the workflow. The receiver must return HTTP 200 within 10 seconds.
Agent 2 trigger mechanism
Triggered internally by Agent 1 on successful deal creation (acknowledgement email and Slack alert path) and by Agent 3 on Closed Won event (reward notification email path). Agent 2 does not have its own external trigger endpoint; it is invoked by the orchestration layer as a sub-workflow or chained step.
Agent 3 trigger mechanism
Webhook (push). HubSpot fires a deal.propertyChange event to the orchestration layer's HubSpot event receiver URL when any deal's dealstage property changes. Signature validation uses HMAC-SHA256 with the HubSpot app client secret, verified against the X-HubSpot-Signature-V3 header. The workflow filters events to referral-tagged deals only (referral_source property non-null) before processing.
Idempotency
Agent 1 uses the Typeform response token as an idempotency key, stored in a short-lived cache (TTL 24 hours) to prevent duplicate processing on Typeform webhook retry. Agent 3 uses the HubSpot event objectId plus occurredAt as a composite idempotency key for deal stage events.
Logging
All workflow executions are logged with timestamp, trigger payload hash, execution status (success/error), and any API response codes. Logs are retained for a minimum of 90 days. Error events additionally post a Slack alert to SLACK_EXCEPTION_CHANNEL_ID.
Credential store: all 38 entries must be populated before any production workflow is activated
// Credential store contents (all values encrypted at rest)

// Typeform
TYPEFORM_OAUTH_ACCESS_TOKEN      = 'tfp_...'
TYPEFORM_WEBHOOK_SECRET          = '<32-char HMAC secret from Typeform Connect>'
TYPEFORM_FORM_ID                 = '<form ID from Typeform dashboard>'
TYPEFORM_FIELD_REF_REFERRER_NAME         = '<field ref string>'
TYPEFORM_FIELD_REF_REFERRER_EMAIL        = '<field ref string>'
TYPEFORM_FIELD_REF_REFERRED_NAME         = '<field ref string>'
TYPEFORM_FIELD_REF_REFERRED_EMAIL        = '<field ref string>'
TYPEFORM_FIELD_REF_REFERRED_COMPANY      = '<field ref string>'
TYPEFORM_FIELD_REF_REFERRAL_NOTES        = '<field ref string>'

// HubSpot
HUBSPOT_PRIVATE_APP_TOKEN        = 'pat-na1-...'
HUBSPOT_PORTAL_ID                = '<HubSpot portal/account ID>'
HUBSPOT_PIPELINE_ID              = '<sales pipeline ID>'
HUBSPOT_CLOSED_WON_STAGE_ID      = '<Closed Won stage ID>'
HUBSPOT_APP_CLIENT_SECRET        = '<used for webhook signature validation>'
HUBSPOT_DEAL_URL_PATTERN         = 'https://app.hubspot.com/contacts/{portalId}/deal/{dealId}'

// Google Sheets
GOOGLE_SERVICE_ACCOUNT_JSON      = '<full JSON key object as string>'
GSHEETS_SPREADSHEET_ID           = '<spreadsheet ID from URL>'
GSHEETS_REGISTER_TAB             = 'Referral Register'

// Gmail
GMAIL_CLIENT_ID                  = '<OAuth client ID>'
GMAIL_CLIENT_SECRET              = '<OAuth client secret>'
GMAIL_REFRESH_TOKEN              = '<offline refresh token>'
GMAIL_SENDER_ADDRESS             = 'referrals@[YourCompany.com]'
GMAIL_SENDER_NAME                = '[YourCompany.com] Referral Programme'
GMAIL_REPLY_TO                   = 'sales@[YourCompany.com]'
GMAIL_TEMPLATE_ACKNOWLEDGEMENT   = '<multiline plain text template string>'
GMAIL_TEMPLATE_REWARD_NOTIFICATION = '<multiline plain text template string>'

// Slack
SLACK_BOT_TOKEN                  = 'xoxb-...'
SLACK_SALES_CHANNEL_ID           = 'C...'
SLACK_EXCEPTION_CHANNEL_ID       = 'C...'

// Xero
XERO_CLIENT_ID                   = '<Xero app client ID>'
XERO_CLIENT_SECRET               = '<Xero app client secret>'
XERO_REFRESH_TOKEN               = '<current offline refresh token>'
XERO_TENANT_ID                   = '<Xero organisation tenant ID>'
XERO_REWARD_ACCOUNT_CODE         = '420'
XERO_REWARD_TIER_1_AMOUNT        = '500'
XERO_REWARD_TIER_2_AMOUNT        = '250'
XERO_REWARD_TIER_3_AMOUNT        = '100'
XERO_CONTACT_MAP                 = '{"referrer@example.com": "<XeroContactID>", ...}'
Integration and API SpecPage 4 of 5
FS-DOC-05Technical

05Error handling and retry logic

Unhandled exceptions must never fail silently. Every integration point must have a defined failure behaviour. If a step cannot complete after exhausting its retry budget, it must log the full error, post a Slack alert to SLACK_EXCEPTION_CHANNEL_ID with the trigger payload reference, and halt the workflow branch cleanly. Do not proceed to downstream steps after an unresolved failure.
Integration
Scenario
Required behaviour
Typeform inbound webhook
Signature validation fails (HMAC mismatch)
Reject with HTTP 401. Log the raw X-Typeform-Signature header and request IP. Do not process the payload. Post alert to SLACK_EXCEPTION_CHANNEL_ID. No retry (validation failure is not transient).
Typeform inbound webhook
Duplicate submission token detected in idempotency cache
Return HTTP 200 to Typeform (prevent retry loop). Log the duplicate token and discard the payload silently. No Slack alert needed unless the duplicate rate exceeds 3 per hour, which should trigger an alert.
HubSpot: contact search
API returns 429 (rate limit)
Retry with exponential backoff: wait 1s, 2s, 4s, 8s (max 4 attempts). If all retries exhausted, halt workflow, log error with the referred contact email, and post Slack alert. The Typeform submission data must be preserved in the log for manual replay.
HubSpot: contact or deal creation
API returns 5xx server error
Retry up to 3 times with 5-second fixed delay. If all retries fail, log the full request body and response, post Slack alert to SLACK_EXCEPTION_CHANNEL_ID with referrer and referred contact names, and halt. Finance and sales team are not notified automatically; the Slack alert is the manual fallback trigger.
HubSpot: contact search
Referred contact email is missing from Typeform payload
Skip duplicate check. Do not create a contact. Post a Slack alert to SLACK_EXCEPTION_CHANNEL_ID flagging the incomplete submission with the referrer name and any other available fields. Halt workflow. Manual review required before record creation.
HubSpot: deal stage webhook
Event received for a deal with no referral_source property
Filter and discard silently. Log the objectId and occurredAt for audit. No Slack alert. This is expected behaviour for non-referral deals and must not generate noise.
Google Sheets: row append
API returns 403 (service account lacks editor permission)
Halt the step. Log the error. Post Slack alert to SLACK_EXCEPTION_CHANNEL_ID. Do not retry until permission is confirmed restored. This is a configuration error, not a transient failure. All subsequent intake submissions will fail until resolved.
Google Sheets: row update
referral_register_row_id is null or missing on the HubSpot deal
Do not append a new row. Log the missing row ID with the deal ID and stage change timestamp. Post Slack alert for manual row identification and update. Halt the update step only; the Xero bill creation step may still proceed if the event is Closed Won.
Gmail: send acknowledgement or reward email
Referrer email address is missing or fails format validation
Do not attempt API call. Log the validation failure with the HubSpot deal ID and referrer name. Post Slack alert to SLACK_EXCEPTION_CHANNEL_ID for manual email follow-up. Continue other steps in the workflow branch (e.g. Slack alert to sales rep is independent).
Gmail: send email
API returns 401 (token expired or revoked)
Attempt one token refresh using GMAIL_REFRESH_TOKEN. If refresh succeeds, retry the send once. If refresh fails or the retry returns 401, halt the step, log the error, and post Slack alert. The refresh token must be re-authorised manually by the sending account owner. Contact support@gofullspec.com if this occurs post-launch.
Slack: chat.postMessage
API returns not_in_channel error
Log the error with the channel ID and message content. Do not retry. Post a fallback alert to SLACK_EXCEPTION_CHANNEL_ID if that channel is reachable; if not, log only. The bot must be added to the target channel manually before the workflow is re-enabled.
Xero: bill creation
Referrer not found in XERO_CONTACT_MAP
Halt the bill creation step. Do not create an unlinked bill. Log the referrer email and HubSpot deal ID. Post Slack alert to SLACK_EXCEPTION_CHANNEL_ID with referrer name and deal close date. Manual fallback: finance contact creates the bill manually in Xero and adds the referrer to XERO_CONTACT_MAP before next occurrence.
Xero: bill creation
API returns 429 (minute-level rate limit)
Read Retry-After header value. Wait the specified number of seconds, then retry once. If the retry also returns 429, wait 60 seconds and retry a final time. After 3 failures, log and post Slack alert. The bill is not created; manual creation is the fallback. The Reward Notification email must not be sent until bill creation is confirmed.
Xero: OAuth token refresh
Refresh token is expired or revoked (tokens expire after 60 days of non-use)
Halt all Xero-dependent steps. Log the error. Post Slack alert to SLACK_EXCEPTION_CHANNEL_ID. The FullSpec team must re-authorise the Xero OAuth connection and update XERO_REFRESH_TOKEN in the credential store. This is a scheduled maintenance risk; set a calendar reminder to perform a test Xero API call every 45 days to prevent token expiry.
All Slack error alerts posted to SLACK_EXCEPTION_CHANNEL_ID must include: the agent name, the step that failed, the timestamp, the relevant record identifiers (HubSpot deal ID and referrer name where available), and a short plain-English description of the required manual action. Generic 'something went wrong' alerts are not acceptable.
Integration and API SpecPage 5 of 5

More documents for this process

Every document generated for Referral Programme Management.

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