Back to Campaign Performance Reporting

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

Campaign Performance Reporting

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

This document is the authoritative technical reference for every integration used in the Campaign Performance Reporting automation. It covers authentication, exact OAuth scopes, webhook and trigger setup, field mappings, credential storage, orchestration layout, and failure handling. The FullSpec team uses this specification during build and testing. Nothing in this document should be interpreted as requiring action from an external party: FullSpec owns the full build end to end.

01Tool inventory

Tool
Role in automation
Auth method
Min plan required
Used by agent(s)
Orchestration layer
Workflow scheduling, agent sequencing, credential store, retry logic
Internal service account
N/A (build tooling)
All agents
Meta Ads Manager
Source of paid social campaign impressions, clicks, spend, and conversions
OAuth 2.0 via Meta App (long-lived user access token)
Business Manager account with Advertiser or above role
Agent 1 (Data Collection)
Google Analytics
Web traffic, session, goal completion, and traffic source data
OAuth 2.0 via Google service account (server-to-server)
Google Analytics 4 property (free)
Agent 1 (Data Collection)
HubSpot
CRM contact and deal attribution filtered by campaign source and date
OAuth 2.0 private app token
HubSpot Marketing Hub Starter or above (API access required)
Agent 1 (Data Collection)
Google Sheets
Central staging store, normalised dataset, derived metric calculations
OAuth 2.0 via Google service account (same as GA4)
Google Workspace or personal Google account (free)
Agent 2 (Normalisation and Insight)
Google Looker Studio
Live stakeholder dashboard, refreshed from Sheets data source
Google service account (shared Sheets connection; no direct API call)
Looker Studio free tier
Agent 3 (Distribution)
Slack
Automated report distribution channel for top-line numbers and dashboard link
Slack Bot OAuth token (Bot Token Scopes)
Slack free tier or above
Agent 3 (Distribution)
Before you connect anything: every integration must be validated against a sandbox or non-production environment before production credentials are loaded into the credential store. For Meta this means a test ad account under the same Business Manager. For HubSpot use a sandbox portal or a developer account. For Google services use a separate GA4 property and a test Sheets file. Only after each sandbox connection returns clean data should production credentials be swapped in.
Integration and API SpecPage 1 of 4
FS-DOC-05Technical

02Per-tool integration detail

Meta Ads Manager

The automation calls the Meta Marketing API (v18.0 or later) on a schedule to retrieve campaign-level and ad-set-level performance metrics for the reporting window. All requests are authenticated with a long-lived user access token that must be refreshed before the 60-day expiry.

Auth method
OAuth 2.0. A Meta App is created in the Business Manager with the ads_read permission. The user access token is exchanged for a long-lived token (valid 60 days) via the token debug and extend endpoint. The long-lived token is stored in the credential store under the key META_ACCESS_TOKEN. A scheduled job must refresh it every 50 days.
Required scopes
ads_read, business_management, read_insights
Webhook / trigger setup
No inbound webhook. The orchestration layer polls the Meta Marketing API on a time-based schedule. Weekly trigger: every Monday at 06:00 UTC, covering the previous Mon-Sun window. Monthly trigger: 1st of each month at 06:00 UTC, covering the previous calendar month. Date ranges are passed as time_range parameters in the API request body.
Required configuration
Store in credential store: META_AD_ACCOUNT_ID (format act_XXXXXXXXXX), META_APP_ID, META_APP_SECRET, META_ACCESS_TOKEN. Campaign IDs are not hardcoded; the agent fetches all campaigns under the account and filters by effective_status=ACTIVE. The reporting fields parameter is stored as a config variable: REPORT_FIELDS=[impressions,clicks,spend,cpc,ctr,actions,cost_per_action_type].
Rate limits
The Marketing API enforces a score-based rate limit. At the current volume of approximately 4 report runs per month, each pulling data for a single ad account, total API call volume is well below the default 200 calls per hour threshold. Throttling logic is not required at current volume but the orchestration layer must handle HTTP 429 responses with a 60-second backoff before one retry.
Constraints
App must pass Meta's basic app review before ads_read is granted in production. This review can take 2 to 5 business days. Token expiry at 60 days is hard: the automation will fail silently if the refresh job is not in place. Insights data has a processing delay of up to 48 hours for conversion actions; the report window should end at least 48 hours before the run time to avoid incomplete conversion figures.
// API endpoint
GET https://graph.facebook.com/v18.0/{act_META_AD_ACCOUNT_ID}/insights
// Key request parameters
fields=impressions,clicks,spend,cpc,ctr,actions,cost_per_action_type
time_range={since: YYYY-MM-DD, until: YYYY-MM-DD}
level=campaign
// Sample response fields written to staging sheet
campaign_id | campaign_name | impressions | clicks | spend | ctr | cpc | conversions | cost_per_conversion
Google Analytics (GA4)

The automation queries the Google Analytics Data API v1 using a service account to retrieve session counts, goal completions, and traffic source breakdowns for the reporting period, aligned to the same date window as the Meta pull.

Auth method
OAuth 2.0 server-to-server using a Google service account JSON key file. The service account must be granted Viewer role on the GA4 property. The key file is stored in the credential store under GA_SERVICE_ACCOUNT_JSON. No user interaction is required after initial setup.
Required scopes
https://www.googleapis.com/auth/analytics.readonly
Webhook / trigger setup
No inbound webhook. The orchestration layer triggers this step immediately after the Meta Ads step completes successfully, using the same date range variables (REPORT_START_DATE, REPORT_END_DATE) passed as step-level inputs.
Required configuration
Store in credential store: GA_PROPERTY_ID (format properties/XXXXXXXXX), GA_SERVICE_ACCOUNT_JSON. The dimension and metric arrays are stored as config variables: GA_DIMENSIONS=[sessionSource,sessionMedium,sessionCampaign] and GA_METRICS=[sessions,goalCompletionsAll,bounceRate,averageSessionDuration]. The GA4 property ID must not be hardcoded.
Rate limits
The Analytics Data API quota is 10,000 requests per day per project and 10 concurrent requests. At 4 report runs per month, a single request per run is required. No throttling is needed. The orchestration layer should handle HTTP 429 with a 30-second exponential backoff up to 3 retries.
Constraints
GA4 data is subject to data thresholds (sampling) if the property is on the free tier and the date range exceeds 90 days. For weekly and monthly reports this is not a concern. The service account email address must be manually added to the GA4 property via the Admin interface before the first run. UTM campaign values in GA4 must match the campaign names used in Meta and HubSpot for the normalisation join to work; mismatches produce unmatched rows and must be flagged in the normalisation step.
// API endpoint
POST https://analyticsdata.googleapis.com/v1beta/properties/{GA_PROPERTY_ID}:runReport
// Request body (simplified)
{dimensions:[{name:sessionSource},{name:sessionMedium},{name:sessionCampaign}],
 metrics:[{name:sessions},{name:goalCompletionsAll}],
 dateRanges:[{startDate:REPORT_START_DATE,endDate:REPORT_END_DATE}]}
// Fields written to staging sheet
session_source | session_medium | campaign_name | sessions | goal_completions
HubSpot

The automation uses the HubSpot Contacts and Deals APIs to retrieve contacts and associated deals created within the reporting period, filtered by campaign source UTM values, and maps them to the ad spend records from Meta.

Auth method
HubSpot Private App token (Bearer token). A private app is created inside the HubSpot portal with the required scopes. The token does not expire unless revoked. Stored in credential store under HUBSPOT_PRIVATE_APP_TOKEN.
Required scopes
crm.objects.contacts.read, crm.objects.deals.read, crm.schemas.contacts.read, crm.schemas.deals.read
Webhook / trigger setup
No inbound webhook for this integration. The orchestration layer triggers the HubSpot pull after the GA4 step completes. The query filters by createdate within the reporting window using HubSpot filter groups on the search endpoint.
Required configuration
Store in credential store: HUBSPOT_PRIVATE_APP_TOKEN, HUBSPOT_PORTAL_ID. Pipeline and deal stage IDs must be stored as config variables (HS_PIPELINE_ID, HS_CLOSED_WON_STAGE_ID) rather than hardcoded. Contact properties to retrieve: hs_analytics_source, hs_analytics_source_data_1, hs_analytics_source_data_2 (these carry the UTM source, medium, and campaign values). Deal properties to retrieve: dealname, amount, dealstage, closedate, hs_analytics_source.
Rate limits
HubSpot enforces 100 requests per 10 seconds per token for private apps. The search endpoint returns up to 100 records per page. At 4 runs per month with typical volumes of under 500 contacts per period, pagination adds 5 to 10 additional requests. Total is well within limits. No throttling required but the agent must implement cursor-based pagination using the paging.next.after token from each response.
Constraints
HubSpot last-touch attribution will not reconcile exactly with Meta click-based attribution. The normalisation agent must surface both figures side by side; a tolerance column showing the variance percentage should be written to the sheet. Contacts without a populated hs_analytics_source_data_2 (UTM campaign) cannot be matched to a campaign and must be written to a separate unmatched_contacts tab in the staging sheet for manual review.
// API endpoint (contacts search)
POST https://api.hubapi.com/crm/v3/objects/contacts/search
// Filter: contacts created in reporting window with campaign source populated
{filterGroups:[{filters:[{propertyName:createdate,operator:BETWEEN,
  value:REPORT_START_EPOCH,highValue:REPORT_END_EPOCH},
  {propertyName:hs_analytics_source_data_2,operator:HAS_PROPERTY}]}],
 properties:[hs_analytics_source,hs_analytics_source_data_1,hs_analytics_source_data_2,
  email,createdate],limit:100,after:CURSOR}
// Fields written to staging sheet
contact_id | hs_source | hs_medium | hs_campaign | createdate | associated_deal_id | deal_amount
Google Sheets

Google Sheets serves as the central staging and normalisation store. The Data Collection Agent writes raw outputs from all three platforms to a staging sheet. The Normalisation and Insight Agent reads from staging, transforms the data, and writes the clean normalised dataset and derived KPIs to the master sheet.

Auth method
OAuth 2.0 server-to-server using the same Google service account JSON key file used for GA4 (GA_SERVICE_ACCOUNT_JSON). The service account must be granted Editor role on the target spreadsheet.
Required scopes
https://www.googleapis.com/auth/spreadsheets, https://www.googleapis.com/auth/drive.file
Webhook / trigger setup
The orchestration layer monitors for a completion flag written to cell STAGING_FLAG_CELL (default: Staging!A1) by the Data Collection Agent. When this cell is set to READY, the Normalisation and Insight Agent is triggered. This is a poll-based check executed every 60 seconds with a timeout of 15 minutes before an error is raised.
Required configuration
Store in credential store: GA_SERVICE_ACCOUNT_JSON, SHEETS_SPREADSHEET_ID. Sheet tab names are stored as config variables: STAGING_SHEET_NAME=Staging, MASTER_SHEET_NAME=Master, UNMATCHED_SHEET_NAME=Unmatched, COMMENTARY_CELL=Master!B2. The Sheets structure requires the following tabs: Staging (raw appended rows), Master (normalised single-row-per-campaign output), Unmatched (unmatched HubSpot contacts), Commentary (AI-drafted plain-English summary paragraph). Column headers must not be modified after go-live; any schema change must go through a controlled update process.
Rate limits
The Sheets API allows 300 read and 300 write requests per minute per project. At current volume the normalisation step makes fewer than 20 API calls per run. No throttling needed. Batch write operations (batchUpdate) should be used wherever possible to minimise call count.
Constraints
Looker Studio is connected to the Master tab as its data source. If column positions or header names change, the Looker Studio dashboard will break. All schema changes must be tested against a duplicate sheet before being applied to the production spreadsheet. The staging sheet should be cleared at the start of each run before new data is appended, to prevent duplicate rows from accumulating.
// Write raw data to staging (Data Collection Agent)
POST https://sheets.googleapis.com/v4/spreadsheets/{SHEETS_SPREADSHEET_ID}/values/{STAGING_SHEET_NAME}!A1:append
// Read staging and write normalised output (Normalisation Agent)
GET  https://sheets.googleapis.com/v4/spreadsheets/{SHEETS_SPREADSHEET_ID}/values/{STAGING_SHEET_NAME}!A:Z
PUT  https://sheets.googleapis.com/v4/spreadsheets/{SHEETS_SPREADSHEET_ID}/values/{MASTER_SHEET_NAME}!A1
// Set completion flag
PUT  https://sheets.googleapis.com/v4/spreadsheets/{SHEETS_SPREADSHEET_ID}/values/Staging!A1
// Body: {values:[[READY]]}
Google Looker Studio

Looker Studio does not expose a direct refresh API. The dashboard is kept live by virtue of its connection to the Master tab in Google Sheets. When the Normalisation Agent writes updated data to the Master sheet, Looker Studio reflects the new values on the next viewer load or scheduled refresh.

Auth method
No direct API authentication required. The Looker Studio data source must be configured to connect to the Google Sheets file using the same service account that writes to it, or via the process owner's Google account with viewer permissions granted to all dashboard recipients.
Required scopes
No additional API scopes. Looker Studio reads from Google Sheets using its native connector. The Sheets service account (GA_SERVICE_ACCOUNT_JSON) must have at minimum Reader access on the spreadsheet for the Looker Studio connector to function when authenticated via service account.
Webhook / trigger setup
The Distribution Agent does not call a Looker Studio API. Instead, it confirms that the Master sheet write has completed (by reading the MASTER_FLAG_CELL value), then proceeds to post the Slack message. The Looker Studio dashboard URL is a static value stored in config as LOOKER_DASHBOARD_URL and is appended to the Slack message as a hyperlink.
Required configuration
Store as config variable (not in credential store): LOOKER_DASHBOARD_URL (the shareable dashboard link). The Looker Studio report must already be connected to the Master sheet tab before go-live. Share settings on the Looker Studio report must be set to Anyone with the link can view, or the relevant stakeholders must be individually granted access.
Rate limits
No API rate limits apply. Looker Studio data source refresh is governed by the Sheets connector cache, which by default refreshes every 15 minutes. For near-real-time accuracy after each report run, the data source can be set to refresh every 15 minutes in the Looker Studio connector settings.
Constraints
If the Master sheet schema changes (column headers renamed or reordered), all Looker Studio chart field bindings will break and must be manually re-mapped inside the Looker Studio editor. This is the single most common post-launch breakage point and must be included in the SOP for the process owner.
// No direct API call. Distribution Agent confirmation check:
GET https://sheets.googleapis.com/v4/spreadsheets/{SHEETS_SPREADSHEET_ID}/values/Master!A1
// Expected response value to proceed:
{values:[[UPDATED]]}
// Dashboard URL appended to Slack message:
LOOKER_DASHBOARD_URL = https://lookerstudio.google.com/reporting/{REPORT_ID}
Slack

The Distribution Agent posts the formatted report summary to a designated Slack channel using the Slack Web API. The message includes top-line metric callouts, any underperformance flags, and a hyperlink to the Looker Studio dashboard.

Auth method
Slack Bot Token (xoxb-...) obtained by installing a Slack App with the required bot token scopes into the target workspace. The token is stored in the credential store under SLACK_BOT_TOKEN.
Required scopes
chat:write, chat:write.public (if posting to channels the bot has not been explicitly invited to)
Webhook / trigger setup
No inbound webhook. The Distribution Agent calls the Slack Web API chat.postMessage endpoint after confirming the Looker Studio flag is set. The channel ID (not channel name) is stored in config as SLACK_CHANNEL_ID to avoid breakage if the channel is renamed.
Required configuration
Store in credential store: SLACK_BOT_TOKEN. Store as config variables: SLACK_CHANNEL_ID (format C0XXXXXXXXX), SLACK_REPORT_TITLE_PREFIX (e.g. Weekly Campaign Report or Monthly Executive Summary, set by report type). The message template uses Block Kit JSON. The template structure is stored as a config variable SLACK_BLOCK_TEMPLATE with placeholders: {{top_performer}}, {{underperformer}}, {{total_spend}}, {{total_conversions}}, {{cost_per_lead}}, {{dashboard_url}}, {{report_period}}.
Rate limits
Slack's chat.postMessage rate limit is 1 request per second per channel. At 4 posts per month this limit is not remotely approached. No throttling required. The orchestration layer must handle HTTP 429 with a 5-second backoff and one retry.
Constraints
The Slack App must be installed into the workspace by a workspace admin. The bot must be invited to the target channel with /invite @BotName before it can post. If the channel is private, the bot must be explicitly a member. The SLACK_CHANNEL_ID must be the channel's numeric ID, not the display name, to survive channel renames.
// API endpoint
POST https://slack.com/api/chat.postMessage
// Headers
Authorization: Bearer {SLACK_BOT_TOKEN}
Content-Type: application/json
// Payload (Block Kit, simplified)
{channel: SLACK_CHANNEL_ID,
 blocks: [
   {type:header, text:{type:plain_text, text:SLACK_REPORT_TITLE_PREFIX - report_period}},
   {type:section, fields:[
     {type:mrkdwn, text:*Total Spend:* total_spend},
     {type:mrkdwn, text:*Conversions:* total_conversions},
     {type:mrkdwn, text:*Cost per Lead:* cost_per_lead},
     {type:mrkdwn, text:*Top Performer:* top_performer}]},
   {type:section, text:{type:mrkdwn,
     text::warning: Underperformer: underperformer}},
   {type:actions, elements:[{type:button, text:{type:plain_text,
     text:View Dashboard}, url:dashboard_url}]}
 ]}
Integration and API SpecPage 2 of 4
FS-DOC-05Technical

03Field mappings between tools

The tables below define the exact field mappings at each agent handoff. Source and destination field names are given in monospace format exactly as they appear in the API response or sheet column header. The normalisation agent is responsible for all renaming and type coercion.

Handoff A: Data Collection Agent to Staging Sheet (Meta Ads Manager)

Source tool
Source field
Destination tool
Destination field
Meta Ads Manager
`campaign_id`
Google Sheets (Staging)
`meta_campaign_id`
Meta Ads Manager
`campaign_name`
Google Sheets (Staging)
`meta_campaign_name`
Meta Ads Manager
`impressions`
Google Sheets (Staging)
`meta_impressions`
Meta Ads Manager
`clicks`
Google Sheets (Staging)
`meta_clicks`
Meta Ads Manager
`spend`
Google Sheets (Staging)
`meta_spend_usd`
Meta Ads Manager
`ctr`
Google Sheets (Staging)
`meta_ctr_pct`
Meta Ads Manager
`cpc`
Google Sheets (Staging)
`meta_cpc_usd`
Meta Ads Manager
`actions[action_type=offsite_conversion.fb_pixel_purchase].value`
Google Sheets (Staging)
`meta_conversions`
Meta Ads Manager
`cost_per_action_type[action_type=offsite_conversion.fb_pixel_purchase].value`
Google Sheets (Staging)
`meta_cost_per_conversion_usd`
Meta Ads Manager
`date_start`
Google Sheets (Staging)
`report_period_start`
Meta Ads Manager
`date_stop`
Google Sheets (Staging)
`report_period_end`

Handoff B: Data Collection Agent to Staging Sheet (Google Analytics)

Source tool
Source field
Destination tool
Destination field
Google Analytics
`sessionCampaign`
Google Sheets (Staging)
`ga_campaign_name`
Google Analytics
`sessionSource`
Google Sheets (Staging)
`ga_source`
Google Analytics
`sessionMedium`
Google Sheets (Staging)
`ga_medium`
Google Analytics
`sessions`
Google Sheets (Staging)
`ga_sessions`
Google Analytics
`goalCompletionsAll`
Google Sheets (Staging)
`ga_goal_completions`
Google Analytics
`bounceRate`
Google Sheets (Staging)
`ga_bounce_rate_pct`
Google Analytics
`averageSessionDuration`
Google Sheets (Staging)
`ga_avg_session_duration_s`

Handoff C: Data Collection Agent to Staging Sheet (HubSpot)

Source tool
Source field
Destination tool
Destination field
HubSpot
`id` (contact object)
Google Sheets (Staging)
`hs_contact_id`
HubSpot
`properties.hs_analytics_source`
Google Sheets (Staging)
`hs_source`
HubSpot
`properties.hs_analytics_source_data_1`
Google Sheets (Staging)
`hs_medium`
HubSpot
`properties.hs_analytics_source_data_2`
Google Sheets (Staging)
`hs_campaign`
HubSpot
`properties.createdate`
Google Sheets (Staging)
`hs_contact_created_date`
HubSpot
`associations.deals[0].id`
Google Sheets (Staging)
`hs_associated_deal_id`
HubSpot
`deal.properties.amount`
Google Sheets (Staging)
`hs_deal_value_usd`
HubSpot
`deal.properties.dealstage`
Google Sheets (Staging)
`hs_deal_stage`

Handoff D: Normalisation and Insight Agent (Staging Sheet to Master Sheet)

Source tool
Source field
Destination tool
Destination field
Google Sheets (Staging)
`meta_campaign_name`
Google Sheets (Master)
`campaign_name`
Google Sheets (Staging)
`meta_spend_usd`
Google Sheets (Master)
`total_spend_usd`
Google Sheets (Staging)
`meta_impressions`
Google Sheets (Master)
`impressions`
Google Sheets (Staging)
`meta_clicks`
Google Sheets (Master)
`clicks`
Google Sheets (Staging)
`meta_conversions`
Google Sheets (Master)
`platform_conversions`
Google Sheets (Staging)
`ga_sessions`
Google Sheets (Master)
`sessions`
Google Sheets (Staging)
`ga_goal_completions`
Google Sheets (Master)
`ga_goal_completions`
Google Sheets (Staging)
`hs_contact_id` (count per campaign)
Google Sheets (Master)
`hs_leads_generated`
Google Sheets (Staging)
`hs_deal_value_usd` (sum per campaign)
Google Sheets (Master)
`hs_pipeline_value_usd`
Derived (formula)
`meta_spend_usd / hs_leads_generated`
Google Sheets (Master)
`cost_per_lead_usd`
Derived (formula)
`hs_pipeline_value_usd / meta_spend_usd`
Google Sheets (Master)
`roas`
Derived (formula)
`meta_clicks / meta_impressions`
Google Sheets (Master)
`ctr_pct`

Handoff E: Distribution Agent (Master Sheet to Slack message)

Source tool
Source field
Destination tool
Destination field
Google Sheets (Master)
`campaign_name` (max roas row)
Slack Block Kit
`{{top_performer}}`
Google Sheets (Master)
`campaign_name` (min roas row, below threshold)
Slack Block Kit
`{{underperformer}}`
Google Sheets (Master)
SUM(`total_spend_usd`)
Slack Block Kit
`{{total_spend}}`
Google Sheets (Master)
SUM(`platform_conversions`)
Slack Block Kit
`{{total_conversions}}`
Google Sheets (Master)
AVERAGE(`cost_per_lead_usd`)
Slack Block Kit
`{{cost_per_lead}}`
Config variable
`LOOKER_DASHBOARD_URL`
Slack Block Kit
`{{dashboard_url}}`
Config variable
`report_period_start` + `report_period_end`
Slack Block Kit
`{{report_period}}`
Integration and API SpecPage 3 of 4
FS-DOC-05Technical

04Build stack and orchestration

Orchestration layout
Three discrete workflows, one per agent. Each workflow is independently deployable and restartable. Workflows share a single centralised credential store; no credential value is duplicated across workflows. Inter-agent handoffs are co-ordinated via flag cells in Google Sheets (STAGING_FLAG_CELL, MASTER_FLAG_CELL) rather than direct workflow-to-workflow calls, so each agent can be rerun independently without triggering the full chain.
Agent 1 trigger (Data Collection Agent)
Time-based schedule poll. Weekly: Monday 06:00 UTC. Monthly: 1st of month 06:00 UTC. The orchestration platform's native scheduler fires the workflow. No inbound webhook. Report type (weekly or monthly) is passed as a runtime variable (REPORT_TYPE) which determines the date range calculation and the SLACK_REPORT_TITLE_PREFIX used downstream.
Agent 2 trigger (Normalisation and Insight Agent)
Poll-based. The orchestration layer polls the Sheets API for the value of STAGING_FLAG_CELL (Staging!A1) every 60 seconds. When the value equals READY the normalisation workflow begins. Timeout: 15 minutes. If READY is not detected within 15 minutes the workflow raises an error, writes TIMEOUT to STAGING_FLAG_CELL, and sends an alert to the error notification channel.
Agent 3 trigger (Distribution Agent)
Poll-based. Polls MASTER_FLAG_CELL (Master!A1) every 60 seconds after Agent 2 is expected to complete. When value equals UPDATED the distribution workflow begins. Timeout: 10 minutes. Signature validation: not applicable (no inbound webhook in this flow). The Slack post is the terminal step; no downstream flag is written after posting.
Credential store type
The orchestration platform's native encrypted secret store. All secrets are referenced by environment variable name at runtime. No credential value appears in any workflow configuration, step definition, or log output.
Config variables (non-secret)
Stored as non-secret environment variables in the orchestration platform: REPORT_TYPE, REPORT_START_DATE, REPORT_END_DATE, META_AD_ACCOUNT_ID, GA_PROPERTY_ID, HS_PIPELINE_ID, HS_CLOSED_WON_STAGE_ID, SHEETS_SPREADSHEET_ID, STAGING_SHEET_NAME, MASTER_SHEET_NAME, UNMATCHED_SHEET_NAME, STAGING_FLAG_CELL, MASTER_FLAG_CELL, COMMENTARY_CELL, SLACK_CHANNEL_ID, SLACK_REPORT_TITLE_PREFIX, LOOKER_DASHBOARD_URL, SLACK_BLOCK_TEMPLATE, ROAS_UNDERPERFORMANCE_THRESHOLD, CPL_UNDERPERFORMANCE_THRESHOLD
Credential store reference: keys, descriptions, and rotation schedule
// CREDENTIAL STORE CONTENTS
// All values are encrypted at rest. Reference by key name only in workflow config.

META_ACCESS_TOKEN          = <long-lived user access token, refresh every 50 days>
META_APP_ID                = <Meta App ID from Business Manager>
META_APP_SECRET            = <Meta App Secret from Business Manager>
GA_SERVICE_ACCOUNT_JSON    = <full JSON key file content for Google service account>
HUBSPOT_PRIVATE_APP_TOKEN  = <HubSpot private app Bearer token>
SLACK_BOT_TOKEN            = <Slack Bot Token, format xoxb-...>

// ROTATION SCHEDULE
META_ACCESS_TOKEN          -> refresh every 50 days via token extend endpoint
GA_SERVICE_ACCOUNT_JSON    -> rotate key annually or on team member offboarding
HUBSPOT_PRIVATE_APP_TOKEN  -> does not expire; rotate on team member offboarding
SLACK_BOT_TOKEN            -> does not expire; rotate on team member offboarding

// NEVER store the following in workflow config or logs:
// META_APP_SECRET, GA_SERVICE_ACCOUNT_JSON private_key field, SLACK_BOT_TOKEN
The META_ACCESS_TOKEN is the only credential with a hard expiry. A dedicated scheduled job must call the Meta token debug endpoint every 50 days to exchange the existing token for a new long-lived token and update the credential store entry. If this job fails and the token expires, the Data Collection Agent will return HTTP 401 on its next run and the entire chain will halt. This job must have its own alerting.

05Error handling and retry logic

Every integration point has a defined failure behaviour. Unhandled exceptions must never fail silently. All errors that halt a workflow must write a status flag to the relevant Sheets flag cell and post an alert to the designated error Slack channel (separate from the report channel) using a minimal fallback Slack webhook that does not depend on the SLACK_BOT_TOKEN.

Integration
Scenario
Required behaviour
Meta Ads Manager
HTTP 401 Unauthorized (token expired or revoked)
Halt workflow immediately. Write ERROR_META_AUTH to STAGING_FLAG_CELL. Post alert to error channel with message: Meta access token invalid. Manual token refresh required. Do not retry automatically as retries will not resolve an auth failure.
Meta Ads Manager
HTTP 429 Too Many Requests
Wait 60 seconds, retry once. If second attempt also returns 429, halt workflow, write ERROR_META_RATE to STAGING_FLAG_CELL, and post alert. Do not loop retry indefinitely.
Meta Ads Manager
HTTP 500 / 503 (Meta API server error)
Retry with exponential backoff: wait 30 seconds, retry; wait 90 seconds, retry; wait 300 seconds, final retry. After 3 failed retries, halt, write ERROR_META_SERVER to STAGING_FLAG_CELL, and alert. The report run should be rescheduled manually for the next business day.
Google Analytics
HTTP 403 Forbidden (service account not added to property)
Halt workflow. Write ERROR_GA_AUTH to STAGING_FLAG_CELL. Post alert: GA4 service account lacks Viewer access on property GA_PROPERTY_ID. This requires manual fix in the GA4 Admin interface. Do not retry.
Google Analytics
Response contains sampled data warning
Do not halt. Log warning to a dedicated run_log tab in the Sheets file with the field sampling_applied=true and the sampling percentage. Continue the workflow but include a note in the Slack message commentary block: Note: GA4 data may be sampled for this period.
HubSpot
HTTP 401 Unauthorized (token revoked)
Halt workflow. Write ERROR_HS_AUTH to STAGING_FLAG_CELL. Post alert: HubSpot private app token is invalid. Token must be regenerated in the HubSpot portal and updated in the credential store. Do not retry.
HubSpot
Pagination cursor missing from response (unexpected end of results)
Log the last successfully retrieved page number to the run_log sheet. Write whatever contacts were retrieved up to that point to the staging sheet. Set a flag hs_pagination_incomplete=true in the staging sheet header row. Continue the workflow but flag in the Slack message that HubSpot contact data may be partial.
Google Sheets
STAGING_FLAG_CELL not set to READY within 15-minute timeout
Halt the Normalisation Agent workflow. Post alert: Data Collection Agent did not complete within expected window. Check Agent 1 run log for errors. Do not attempt normalisation on incomplete staging data.
Google Sheets
Write operation returns HTTP 403 (service account lost Editor access)
Halt workflow. Post alert: Sheets write permission revoked for service account. Check spreadsheet sharing settings. Do not retry as repeated write failures may corrupt the staging sheet.
Google Looker Studio
MASTER_FLAG_CELL not set to UPDATED within 10-minute timeout
Halt Distribution Agent. Post alert: Master sheet was not confirmed as updated. Do not post the Slack report with stale data. The process owner must manually verify the Master sheet and re-trigger the Distribution Agent.
Slack
HTTP 404 channel_not_found (channel renamed or deleted)
Halt Distribution Agent. Post alert to error fallback webhook: Slack channel ID SLACK_CHANNEL_ID not found. Verify SLACK_CHANNEL_ID config variable matches current channel ID. Do not retry with the channel name as a fallback.
Slack
HTTP 429 Too Many Requests
Wait 5 seconds, retry once. This scenario is extremely unlikely at 4 posts per month but must be handled. If retry fails, post alert via error fallback webhook and halt.
All integrations
Unhandled exception (unexpected error type or malformed response)
Catch all unhandled exceptions at the workflow level. Write ERROR_UNHANDLED to the relevant flag cell. Post alert to error channel with the full error message, the workflow name, and the step number where the exception occurred. Never allow an unhandled exception to pass silently. The process owner must be notified for every unhandled exception regardless of severity.
Error alerts must use a fallback Slack incoming webhook URL stored separately from SLACK_BOT_TOKEN. This webhook must target a different channel (e.g. #automation-errors) and must not depend on any of the credentials used in the main workflow. If the main SLACK_BOT_TOKEN is the source of failure, the fallback webhook must still be able to fire. Contact support@gofullspec.com if clarification on error channel setup is needed.
Integration and API SpecPage 4 of 4

More documents for this process

Every document generated for Campaign Performance Reporting.

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