FS-DOC-05Technical
Integration and API Spec
Review and Reputation Management
[YourCompany.com] · Marketing Department · Prepared by FullSpec · [Today's Date]
This document is the authoritative integration reference for the Review and Reputation Management automation. It covers every tool connected to the orchestration layer, exact OAuth scopes, webhook and polling configurations, field mappings across agent handoffs, credential store contents, and defined error-handling behaviour for every integration point. The FullSpec team uses this document as the build source of truth. No credential, scope string, or field mapping should deviate from what is specified here without a version update.
01Tool inventory
Tool
Role in automation
Auth method
Min plan required
Used by agents
Orchestration layer
Hosts all workflows, credential store, polling schedules, and inter-agent routing logic
Internal service account
N/A (platform decision)
All agents
Google Business Profile
Primary review source and response posting target
OAuth 2.0 (Google APIs)
Verified Business account (free)
Agent 1, Agent 2
Trustpilot
Secondary review source
OAuth 2.0 (Trustpilot Business API)
Business (paid) for API write access
Agent 1
Notion
Central review tracker and audit log; read by reporting agent
OAuth 2.0 (Notion Integration)
Plus plan ($16/month)
Agent 1, Agent 3
Slack
Manager escalation alerts and weekly reputation report delivery
OAuth 2.0 (Slack app)
Free tier sufficient
Agent 2, Agent 3
HubSpot
CRM contact matching and timeline activity logging
Private App token (OAuth 2.0)
Starter CRM or above
Agent 3
Gmail
Posting responses via email API for platforms that accept email replies
OAuth 2.0 (Google APIs)
Any Google Workspace or personal account
Agent 2
Before you connect anything: configure and validate every integration against a sandbox or test account before touching production credentials. Use Trustpilot's sandbox environment, a secondary Google Business Profile location, a Notion test workspace, a Slack test channel, a HubSpot sandbox portal, and a Gmail alias. Rotate all credentials out of sandbox before go-live and never commit credentials to version control.
02Per-tool integration detail
Google Business Profile
Used by the Review Classifier Agent (Agent 1) to poll for new reviews via the Business Profile API, and by the Response Drafting Agent (Agent 2) to post approved responses. Polling replaces a webhook because the API does not offer push notifications for new reviews.
Auth method
OAuth 2.0. Authorise using the Google APIs OAuth consent screen. Store access_token and refresh_token in the credential store. Refresh token does not expire unless revoked; access token expires after 3600 seconds.
Required scopes
https://www.googleapis.com/auth/business.manage
Polling setup
Poll the accounts/{accountId}/locations/{locationId}/reviews endpoint every 15 minutes. Filter by updateTime descending. Store the updateTime of the last-processed review in the credential store as gbp_last_poll_cursor to avoid reprocessing on each cycle.
Response posting
POST to accounts/{accountId}/locations/{locationId}/reviews/{reviewId}/reply with body {"comment": "<draft_response_text>"}. This requires the same OAuth scope. Only one reply per review is permitted; subsequent calls overwrite the existing reply.
Required configuration
gbp_account_id and gbp_location_id stored in credential store. Do not hardcode. If monitoring multiple locations, store as a JSON array and iterate per workflow run.
Rate limits
The Business Profile API enforces a quota of 10,000 requests/day per project and 1 QPM (query per minute) per location for review reads. At 60-120 reviews/month the polling cadence (96 polls/day at 15-min intervals) is well within quota. No throttling mechanism required at current volume, but implement exponential backoff on 429 responses regardless.
Constraints
API only returns reviews for verified locations. Reviewers' profile photos and profile URLs are returned but must not be stored in Notion or HubSpot for privacy reasons. Deleted reviews may reappear transiently; deduplicate by reviewId before processing.
// Poll response (simplified)
GET accounts/{accountId}/locations/{locationId}/reviews
-> reviews[].reviewId string
-> reviews[].reviewer.displayName string
-> reviews[].starRating enum: ONE|TWO|THREE|FOUR|FIVE
-> reviews[].comment string
-> reviews[].updateTime ISO 8601 timestamp
// Reply POST body
POST .../reviews/{reviewId}/reply
-> {"comment": "<approved_response_text>"}Trustpilot
Secondary review source consumed by Agent 1. Trustpilot's Business API requires a paid Business plan for write access; read access for polling new reviews also requires API credentials issued via Trustpilot's developer portal.
Auth method
OAuth 2.0 with client credentials flow. Exchange client_id and client_secret for a bearer token at https://api.trustpilot.com/v1/oauth/oauth-business-users-for-applications/accesstoken. Token expires after 3600 seconds; implement auto-refresh using the refresh_token stored in credential store.
Required scopes
reviews.read reviews.write business-units.read
Polling setup
Poll GET /v1/business-units/{businessUnitId}/reviews?orderBy=createdat.desc&startDateTime={tp_last_poll_cursor} every 15 minutes. Store tp_last_poll_cursor as an ISO 8601 timestamp in the credential store, updated after each successful poll.
Webhook note
Trustpilot offers webhooks for new reviews on Business and Enterprise plans. If the account is on a qualifying plan, configure a webhook endpoint on the orchestration layer at /webhook/trustpilot/new-review and validate the X-Trustpilot-Signature header (HMAC-SHA256 of the raw request body using the webhook secret stored as tp_webhook_secret). Switch the workflow trigger from poll to webhook to reduce latency.
Required configuration
tp_business_unit_id, tp_client_id, tp_client_secret, tp_access_token, tp_refresh_token, tp_webhook_secret (if webhook enabled) all stored in credential store.
Rate limits
Trustpilot enforces 100 requests/minute per API key. Current polling volume (96 requests/day) is negligible. No throttling required. Backoff on 429.
Constraints
Trustpilot reviewer emails are not exposed via the API; CRM matching for Trustpilot reviews must rely on displayName only, which may produce partial or no matches. Flag this clearly in the Notion record with a match_confidence field of 'low'.
// Poll response (simplified)
GET /v1/business-units/{businessUnitId}/reviews
-> reviews[].id string
-> reviews[].consumer.displayName string
-> reviews[].stars integer (1-5)
-> reviews[].text string
-> reviews[].createdAt ISO 8601 timestamp
// Webhook payload (if enabled)
POST /webhook/trustpilot/new-review
Headers: X-Trustpilot-Signature: <hmac_sha256>
Body: {event: 'review.created', review: {...}}Notion
Central review tracker used by Agent 1 to log each incoming review and by Agent 3 to read aggregate data for the weekly report. A single Notion database serves as the audit log for all review records across platforms.
Auth method
OAuth 2.0 via a Notion Integration. Create a Notion internal integration at https://www.notion.so/my-integrations, share the target database with the integration, and store the notion_api_token in the credential store.
Required scopes
read_content update_content insert_content
Database structure
The review tracker database must contain the following properties: review_id (text, unique), platform (select: Google|Trustpilot), reviewer_name (text), star_rating (number), sentiment (select: positive|neutral|negative), confidence_score (number, 0-1), topic_summary (text), response_draft (text), response_posted (checkbox), response_posted_at (date), approved_by (text), hubspot_contact_id (text), match_confidence (select: high|medium|low|none), created_at (date). The database ID is stored as notion_database_id in the credential store.
Required configuration
notion_api_token and notion_database_id stored in credential store. Do not hardcode database ID. Notion integration must be explicitly shared with the target database by a workspace admin before first run.
Rate limits
Notion API enforces 3 requests/second average. At 120 reviews/month the write volume is minimal. No throttling required. Implement retry with exponential backoff on 429 and 503 responses.
Constraints
Notion API does not support bulk insert; each review must be created as a separate page in a single API call. Relation properties require the related database to also be shared with the integration. Rich text fields are capped at 2000 characters; truncate response drafts that exceed this limit and append a truncation flag property.
// Create page (new review record)
POST https://api.notion.com/v1/pages
Body: {
parent: {database_id: notion_database_id},
properties: {
review_id, platform, reviewer_name, star_rating,
sentiment, confidence_score, topic_summary,
response_draft, created_at
}
}
// Update page (post-response)
PATCH https://api.notion.com/v1/pages/{page_id}
Body: {properties: {response_posted, response_posted_at, approved_by, hubspot_contact_id}}Integration and API SpecPage 1 of 3
FS-DOC-05Technical
Slack
Used by Agent 2 to send manager escalation alerts for negative reviews (2 stars or below), and by Agent 3 to post the weekly reputation summary to the marketing channel. Two distinct Slack channels are targeted.
Auth method
OAuth 2.0 via a Slack App. Create a Slack App in the workspace, install it, and store the bot_token (xoxb-...) as slack_bot_token in the credential store.
Required scopes
chat:write chat:write.public channels:read files:write
Escalation alert setup
For negative reviews, post a Slack message to the channel ID stored as slack_escalation_channel_id. The message payload must include: review platform, star rating, reviewer name, review text (truncated to 500 chars if longer), the AI-generated draft response, and an approval link. Use Slack Block Kit with a Section block for review content and two Button action elements: 'Approve and Post' (value: approve) and 'Edit in Notion' (value: edit). The action callback URL must be registered as a Slack Interactivity endpoint on the automation platform at /webhook/slack/approval-action.
Approval callback validation
Validate every inbound Slack interactivity payload by verifying the X-Slack-Signature header (HMAC-SHA256 of v0:{timestamp}:{raw_body} using slack_signing_secret stored in credential store). Reject any payload where the timestamp is older than 5 minutes.
Weekly report setup
Post to slack_report_channel_id every Monday at 08:00 in the account's local timezone. Report content is assembled by Agent 3 from Notion data and posted as a structured Block Kit message with sections for review volume, average star rating, response rate percentage, and top 3 complaint themes.
Required configuration
slack_bot_token, slack_escalation_channel_id, slack_report_channel_id, slack_signing_secret stored in credential store.
Rate limits
Slack enforces 1 message/second per channel for chat.postMessage. Escalation bursts (multiple negative reviews arriving simultaneously) should be queued with a 1-second delay between posts. At 60-120 reviews/month, this is a rare edge case.
Constraints
Slack button interactions expire after 30 minutes by default; if a manager does not respond within 30 minutes the orchestration layer must re-send the alert or flag the review for manual follow-up. Block Kit interactive messages require the app to be installed in the workspace and have the interactions URL configured.
// Escalation alert post
POST https://slack.com/api/chat.postMessage
channel: slack_escalation_channel_id
blocks: [Section(review_text), Section(draft_response), Actions(approve_btn, edit_btn)]
// Approval callback inbound
POST /webhook/slack/approval-action
Headers: X-Slack-Signature: v0={hmac}, X-Slack-Request-Timestamp: {ts}
Payload: {action_id: 'approve'|'edit', response_url, message_ts, channel_id}
// Weekly report post
POST https://slack.com/api/chat.postMessage
channel: slack_report_channel_id
blocks: [Header, Section(volume), Section(avg_rating), Section(response_rate), Section(themes)]HubSpot
Used exclusively by Agent 3 (CRM and Reporting Agent) to search for a matching contact by name or email and log the review outcome as a timeline activity. Anonymous or unmatched reviewers result in a partial record or a skipped CRM step.
Auth method
HubSpot Private App token. Create a Private App in HubSpot Settings, grant the required scopes, and store the token as hubspot_private_app_token in the credential store. Private App tokens do not expire unless rotated manually.
Required scopes
crm.objects.contacts.read crm.objects.contacts.write timeline.events.write crm.schemas.contacts.read
Contact search
Search for a matching contact using POST /crm/v3/objects/contacts/search. Filter by email (if available from the review platform) or by firstname + lastname (displayName split on first space). If multiple matches are returned, take the most recently active contact. If zero matches, set hubspot_contact_id to null and match_confidence to 'none' in the Notion record; do not create a new contact automatically.
Timeline activity
Log the review outcome using the HubSpot Timeline Events API (POST /crm/v3/timeline/events). Create a custom event type during setup with the following properties: review_platform (string), star_rating (number), sentiment (string), review_text (string, truncated to 1000 chars), response_posted (boolean), response_posted_at (datetime). Store the event type ID as hubspot_event_type_id in the credential store.
Required configuration
hubspot_private_app_token, hubspot_portal_id, hubspot_event_type_id stored in credential store. The timeline event type must be created and its ID recorded before Agent 3 is deployed.
Rate limits
HubSpot enforces 100 requests/10 seconds and 40,000 requests/day on Starter. At 120 reviews/month the daily volume is negligible. No throttling required. Implement retry with backoff on 429 (Retry-After header respected).
Constraints
Trustpilot reviews will not carry reviewer email; contact matching will rely on name only and will produce a higher rate of 'none' matches. Inform the process owner before go-live that CRM update rate for Trustpilot reviews will be lower than for Google reviews. Do not store raw review text in a contact property; use the timeline event only.
// Contact search
POST https://api.hubapi.com/crm/v3/objects/contacts/search
{filterGroups: [{filters: [{propertyName:'email', operator:'EQ', value: reviewer_email}]}]}
-> results[0].id -> hubspot_contact_id
// Timeline event creation
POST https://api.hubapi.com/crm/v3/timeline/events
{eventTemplateId: hubspot_event_type_id,
objectId: hubspot_contact_id,
tokens: {review_platform, star_rating, sentiment, review_text, response_posted, response_posted_at}}Gmail
Used by Agent 2 (Response Drafting Agent) to post responses to review platforms that accept email-based replies rather than direct API write access. Also used as the fallback posting method when the Google Business Profile API write call fails.
Auth method
OAuth 2.0 via Google APIs. Use the same OAuth consent screen as Google Business Profile. Store gmail_access_token and gmail_refresh_token separately from GBP tokens in the credential store to allow independent rotation.
Required scopes
https://www.googleapis.com/auth/gmail.send https://www.googleapis.com/auth/gmail.readonly
Send setup
Construct a MIME message with To, From, Subject, and plain-text body. Base64url-encode the MIME message and POST to https://gmail.googleapis.com/gmail/v1/users/me/messages/send. The From address must match the authenticated Google account. Do not use HTML body formatting; review platform email parsers expect plain text.
Required configuration
gmail_access_token, gmail_refresh_token, gmail_sender_address, gmail_client_id, gmail_client_secret stored in credential store. The sender address must be the same account used to authenticate.
Rate limits
Gmail API enforces 250 quota units/second per user and 25,000 requests/day. Sending one response per review at 120 reviews/month is negligible. No throttling required. Implement retry with exponential backoff on 429 and 500 responses.
Constraints
Gmail cannot be used to post responses directly to Trustpilot; Trustpilot responses must go via the Trustpilot API. Gmail is applicable only to platforms that route review reply notifications through email. Validate the target platform before invoking this integration. Sent messages are stored in the authenticated Gmail account's Sent folder and must not be treated as the canonical record of a posted response; Notion is the record of truth.
// Send message
POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
Authorization: Bearer gmail_access_token
Body: {raw: base64url(<MIME message>)}
MIME headers: To, From, Subject: 'Response to your review on {platform}', Content-Type: text/plain
-> {id: message_id, threadId, labelIds}03Field mappings between tools
The tables below define the exact field mappings for each agent handoff. Source field names are as returned by the source tool's API. Destination field names are as defined in the target tool's schema. All field names are case-sensitive.
Handoff 1: Google Business Profile and Trustpilot to Notion (Review Classifier Agent output)
Source tool
Source field
Destination tool
Destination field
Google Business Profile
`reviews[].reviewId`
Notion
`review_id`
Google Business Profile
`reviews[].reviewer.displayName`
Notion
`reviewer_name`
Google Business Profile
`reviews[].starRating` (enum to int: ONE=1)
Notion
`star_rating`
Google Business Profile
`reviews[].comment`
Notion
`review_text` (agent input only, not stored)
Google Business Profile
`reviews[].updateTime`
Notion
`created_at`
Trustpilot
`reviews[].id`
Notion
`review_id`
Trustpilot
`reviews[].consumer.displayName`
Notion
`reviewer_name`
Trustpilot
`reviews[].stars`
Notion
`star_rating`
Trustpilot
`reviews[].text`
Notion
`review_text` (agent input only, not stored)
Trustpilot
`reviews[].createdAt`
Notion
`created_at`
Classifier Agent output
`sentiment_label`
Notion
`sentiment`
Classifier Agent output
`confidence_score`
Notion
`confidence_score`
Classifier Agent output
`topic_summary`
Notion
`topic_summary`
Static value
`'Google'` or `'Trustpilot'`
Notion
`platform`
Handoff 2: Notion record to Response Drafting Agent (Agent 2 input)
Source tool
Source field
Destination tool
Destination field
Notion
`review_id`
Response Drafting Agent
`review_id`
Notion
`sentiment`
Response Drafting Agent
`sentiment_label`
Notion
`star_rating`
Response Drafting Agent
`star_rating`
Notion
`topic_summary`
Response Drafting Agent
`topic_summary`
Notion
`platform`
Response Drafting Agent
`platform`
Notion
`reviewer_name`
Response Drafting Agent
`reviewer_name`
Credential store
`brand_voice_guidelines`
Response Drafting Agent
`system_prompt_context`
Handoff 3: Response Drafting Agent output to Slack (escalation) and Google Business Profile or Gmail (posting)
Source tool
Source field
Destination tool
Destination field
Response Drafting Agent
`draft_response_text`
Slack
`blocks[].text` (escalation alert body)
Response Drafting Agent
`draft_response_text`
Google Business Profile
`reply.comment`
Response Drafting Agent
`draft_response_text`
Gmail
MIME body (plain text)
Notion
`review_id`
Google Business Profile
`reviewId` (path param)
Notion
`notion_page_id`
Slack
`actions[].value` (Edit in Notion link)
Slack callback
`action_id` ('approve')
Notion
`response_posted` (set to true)
Slack callback
`action_ts`
Notion
`response_posted_at`
Slack callback
`user.name`
Notion
`approved_by`
Handoff 4: Notion tracker to HubSpot and Slack report (CRM and Reporting Agent, Agent 3)
Source tool
Source field
Destination tool
Destination field
Notion
`reviewer_name`
HubSpot
`properties.firstname` + `properties.lastname` (search filter)
Notion
`platform`
HubSpot
`tokens.review_platform`
Notion
`star_rating`
HubSpot
`tokens.star_rating`
Notion
`sentiment`
HubSpot
`tokens.sentiment`
Notion
`topic_summary`
HubSpot
`tokens.review_text`
Notion
`response_posted`
HubSpot
`tokens.response_posted`
Notion
`response_posted_at`
HubSpot
`tokens.response_posted_at`
HubSpot
`results[0].id`
Notion
`hubspot_contact_id`
Notion aggregate query
`count(review_id)` (weekly)
Slack
`blocks[Section].text` (report volume)
Notion aggregate query
`avg(star_rating)` (weekly)
Slack
`blocks[Section].text` (report avg rating)
Notion aggregate query
`count(response_posted=true)/count(*)` (weekly)
Slack
`blocks[Section].text` (report response rate)
Integration and API SpecPage 2 of 3
FS-DOC-05Technical
04Build stack and orchestration
Orchestration layout
Three discrete workflows, one per agent (Review Classifier Agent, Response Drafting Agent, CRM and Reporting Agent). Workflows share a single credential store. Inter-agent data is passed via the Notion database; each workflow reads and writes to the same review record using the Notion page ID as the shared key.
Agent 1 trigger (Review Classifier Agent)
Scheduled poll: every 15 minutes. The workflow reads gbp_last_poll_cursor and tp_last_poll_cursor from the credential store, calls both platform APIs, deduplicates by review_id, and processes each new record sequentially. After processing, both cursors are updated. If a Trustpilot webhook is configured, an additional webhook trigger at /webhook/trustpilot/new-review supplements the poll and should be deduplicated against the same review_id.
Agent 2 trigger (Response Drafting Agent)
Database trigger: fires when a new Notion page is created in the review tracker with sentiment populated. The orchestration layer polls the Notion database for pages where response_draft is empty and sentiment is not null, on a 5-minute interval. After Agent 1 writes a record, Agent 2 picks it up within 5 minutes. For Slack approval callbacks, a webhook trigger at /webhook/slack/approval-action receives the manager's decision and resumes the workflow from a suspended state.
Agent 3 trigger (CRM and Reporting Agent)
Two triggers: (1) Event-based: fires when response_posted is set to true on a Notion page. Polls Notion every 10 minutes for records where response_posted is true and hubspot_contact_id is null (unprocessed CRM step). (2) Scheduled: every Monday at 08:00 local time, assembles the weekly report from Notion aggregate data and posts to Slack.
Slack approval suspension
When Agent 2 posts a negative-review escalation to Slack, the workflow enters a suspended state holding the review_id, draft_response_text, and notion_page_id. The workflow resumes only on receipt of a valid Slack callback at /webhook/slack/approval-action with action_id of 'approve'. If no callback is received within 60 minutes, the workflow re-posts the Slack alert once and then flags the Notion record with a status of 'approval_timeout'.
Credential store
A single encrypted credential store is shared across all three workflows. All secrets below are stored here and referenced by key name. No secret appears in workflow logic or code.
All keys referenced by name in workflow logic. No value is hardcoded outside this store.
// Credential store contents
// Google Business Profile
gbp_client_id = '<oauth_client_id>'
gbp_client_secret = '<oauth_client_secret>'
gbp_access_token = '<current_access_token>'
gbp_refresh_token = '<refresh_token>'
gbp_account_id = '<accounts/{id}>'
gbp_location_id = '<locations/{id}>'
gbp_last_poll_cursor = '<ISO8601 timestamp, updated each poll>'
// Trustpilot
tp_client_id = '<trustpilot_api_key>'
tp_client_secret = '<trustpilot_api_secret>'
tp_access_token = '<current_bearer_token>'
tp_refresh_token = '<refresh_token>'
tp_business_unit_id = '<business_unit_uuid>'
tp_last_poll_cursor = '<ISO8601 timestamp, updated each poll>'
tp_webhook_secret = '<hmac_signing_secret>' // if webhook enabled
// Notion
notion_api_token = '<secret_xxxx>'
notion_database_id = '<database_uuid>'
// Slack
slack_bot_token = '<xoxb-xxxx>'
slack_signing_secret = '<signing_secret>'
slack_escalation_channel_id = '<C0XXXXXXXXX>'
slack_report_channel_id = '<C0YYYYYYYYY>'
// HubSpot
hubspot_private_app_token = '<pat-na1-xxxx>'
hubspot_portal_id = '<portal_integer_id>'
hubspot_event_type_id = '<timeline_event_template_id>'
// Gmail
gmail_client_id = '<oauth_client_id>'
gmail_client_secret = '<oauth_client_secret>'
gmail_access_token = '<current_access_token>'
gmail_refresh_token = '<refresh_token>'
gmail_sender_address = '<replies@yourdomain.com>'
// Agent configuration
brand_voice_guidelines = '<plaintext brand voice doc, max 4000 chars>'
negative_review_threshold = 2 // star_rating <= this value triggers escalation
approval_timeout_minutes = 60The brand_voice_guidelines value is injected into the Response Drafting Agent's system prompt at runtime. It must be updated in the credential store whenever the brand voice document changes. Changes take effect on the next workflow run without a redeployment.
05Error handling and retry logic
Every integration point has a defined failure behaviour. Unhandled exceptions must never fail silently. All retries use exponential backoff starting at 5 seconds unless a Retry-After header specifies otherwise.
Integration
Scenario
Required behaviour
Google Business Profile (poll)
401 Unauthorized (token expired)
Automatically refresh access token using gbp_refresh_token. Retry the poll immediately. If refresh fails, halt workflow, write error to orchestration log, and send a Slack alert to slack_escalation_channel_id with message 'GBP auth failure: manual reauthorisation required'.
Google Business Profile (reply POST)
403 Forbidden (insufficient permission or location not verified)
Do not retry. Mark Notion record response_posted as false and add error note 'GBP reply forbidden'. Route to Gmail fallback if platform supports email reply. Alert FullSpec via support@gofullspec.com if error persists across 3 consecutive reviews.
Google Business Profile (reply POST)
409 Conflict (reply already exists)
Log as informational, not an error. Mark Notion record response_posted as true. Do not post again. This occurs if a duplicate trigger fires; deduplication by reviewId should prevent it but handle defensively.
Trustpilot (poll)
429 Too Many Requests
Respect Retry-After header value. Pause polling loop, wait the specified duration, then retry. Log the throttle event. If sustained across more than 5 consecutive polls, reduce poll frequency to 30 minutes for 1 hour then restore.
Trustpilot (webhook signature validation)
Invalid X-Trustpilot-Signature
Reject the payload with HTTP 401. Do not process the review. Log the rejection with the raw signature value and timestamp. Alert FullSpec support if more than 2 invalid signatures are received within 10 minutes (possible replay attack).
Notion (create review page)
503 Service Unavailable
Retry up to 4 times with exponential backoff (5s, 10s, 20s, 40s). If all retries fail, cache the review payload in the orchestration layer's internal queue and retry after 5 minutes. Do not drop the review. Alert FullSpec support if the queue depth exceeds 10 unwritten records.
Slack (escalation alert)
Message post fails or channel not found
Retry once after 10 seconds. If retry fails, fall back to sending an alert email via Gmail to the address stored as slack_fallback_email (add this key to the credential store). Log the failure. The review must not remain unescalated; a human must be notified by some channel within 30 minutes of detection.
Slack (approval callback)
No callback received within approval_timeout_minutes
Re-send the escalation alert to slack_escalation_channel_id once. If still no response after a further 60 minutes, set Notion field status to 'approval_timeout' and send a Gmail fallback email to slack_fallback_email with the draft and review details. Do not auto-post the response. Log the timeout.
HubSpot (contact search)
Zero matches for reviewer name
Set hubspot_contact_id to null and match_confidence to 'none' in Notion. Do not create a new HubSpot contact. Continue workflow. Log as informational. This is expected behaviour for anonymous or pseudonymous reviewers.
HubSpot (timeline event)
400 Bad Request (invalid event template ID or missing token)
Do not retry immediately. Log the full error response. Alert FullSpec support. This indicates a configuration error (likely hubspot_event_type_id is incorrect or the event template was deleted). Halt the CRM step only; the Slack report step in Agent 3 should still proceed independently.
Gmail (send message)
500 or 503 server error
Retry up to 3 times with backoff (5s, 15s, 45s). If all retries fail, log the failure and mark Notion response_posted as false with error note 'Gmail send failed after retries'. Surface in the weekly Slack report as a count of failed posts for manual follow-up.
All integrations
Unhandled exception or unexpected response shape
Catch all uncaught exceptions at the workflow level. Log the full exception including workflow ID, step name, payload hash, and timestamp to the orchestration log. Send an immediate alert to support@gofullspec.com with the same details. Never allow silent failure. Mark the affected Notion record with status 'error' and the error message.
Retry budgets reset per workflow run, not per day. A review that exhausts all retries across multiple integrations must end in a clearly identifiable error state in Notion, not a partial or ambiguous record. The weekly Slack report should include a count of error-state records as a standing operational metric.
Integration and API SpecPage 3 of 3