Back to Purchase Approval Workflow

Integration and API Spec

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

4 pagesPDF · Technical
FS-DOC-05Technical

Integration and API Spec

Purchase Approval Workflow

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

This document is the definitive technical reference for every integration point in the Purchase Approval Workflow automation. It specifies authentication methods, exact OAuth scopes, webhook configurations, field mappings between tools, credential store contents, orchestration layout, and failure handling for every integration point. The FullSpec team uses this specification as the single source of truth during build and testing. No integration detail should be assumed or improvised beyond what is recorded here.

01Tool inventory

Tool
Role in automation
Auth method
Min plan required
Used by agents
Jotform
Purchase request intake form; webhook trigger source
API key (header)
Starter (free) or Bronze for webhook delivery guarantees
Agent 1 (Request Intake)
Google Sheets
Master purchase register and spend-threshold rules table
OAuth 2.0 (service account recommended)
Any Google Workspace or personal account with Sheets API enabled
Agent 1, Agent 2
Slack
Approver notification via Block Kit; interactive approve/reject buttons
OAuth 2.0 (Slack app with Bot Token)
Free tier sufficient; Pro required for message retention beyond 90 days
Agent 2 (Approval Routing)
Gmail
Escalation emails and requester outcome notifications
OAuth 2.0 (Google Workspace OAuth app or service account with domain delegation)
Google Workspace Business Starter or personal Gmail
Agent 1, Agent 2, Agent 3
Xero
Draft purchase order creation on approved requests
OAuth 2.0 (Xero app with PKCE flow)
Starter plan (PO creation included)
Agent 3 (PO Creation and Notification)
Workflow automation platform
Orchestration layer: hosts all three agent workflows, manages credential store, handles retries and error routing
Platform-native credential vault; no external auth required for orchestration itself
Platform subscription ($60/month per tooling block)
All agents
Before you connect anything: create a sandbox or test environment for every tool listed above and validate every credential and scope combination against that sandbox before any production credentials are entered. Jotform test forms, a Google Sheets staging spreadsheet, a Slack test workspace, a Gmail alias, and the Xero Demo Company should all be used during the build phase. Production credentials are only entered after end-to-end testing passes in the staging environment.

02Per-tool integration detail

Jotform

Source trigger for the entire workflow. A form submission fires a webhook to the orchestration layer, delivering the structured request payload. The orchestration layer does not poll Jotform; it listens for inbound webhook POST requests only.

Auth method
API key passed in the request header as 'APIKEY: <jotform_api_key>'. The key is stored in the credential store under the key JOTFORM_API_KEY. It must never be hardcoded in the workflow definition.
Required scopes
Jotform API keys are not scope-segmented by OAuth. The key must belong to an account with form ownership or team access to the purchase request form. Use a dedicated service account, not a personal user account.
Webhook / trigger setup
In the Jotform form builder, navigate to Settings > Integrations > Webhooks. Add the orchestration layer's inbound webhook URL as the target. Set the method to POST. Jotform signs each delivery with an HMAC-SHA256 signature in the header 'x-jotform-signature'. The orchestration layer must validate this signature using the shared secret stored as JOTFORM_WEBHOOK_SECRET before processing the payload.
Required configuration
Form must include fields: requester_name (text), requester_email (email), department (dropdown), supplier_name (text), amount_usd (number), business_justification (long text), supporting_document (file upload, optional). The form ID is stored as JOTFORM_FORM_ID in the credential store. Field names must match exactly as mapped in Section 03.
Rate limits
Jotform API: 1,000 requests/day on Starter; 10,000/day on Bronze and above. At ~60 submissions/month (2-3/day peak), rate limits are not a concern. No throttling required at current volume.
Hard constraints
File uploads in the supporting_document field are delivered as a URL, not a binary payload. The orchestration layer must store this URL in the Google Sheets register, not attempt to fetch and re-upload the file. Jotform webhook retries up to 3 times on non-2xx responses with exponential backoff; the orchestration endpoint must return 200 immediately on receipt and process asynchronously.
// Inbound webhook payload (abridged)
{
  "formID": "<JOTFORM_FORM_ID>",
  "submissionID": "<unique_submission_id>",
  "rawRequest": {
    "q1_requesterName": "Jane Smith",
    "q2_requesterEmail": "jane.smith@yourcompany.com",
    "q3_department": "Marketing",
    "q4_supplierName": "Acme Supplies Ltd",
    "q5_amountUsd": "1200.00",
    "q6_businessJustification": "Event collateral for Q3 conference",
    "q7_supportingDocument": "https://www.jotform.com/uploads/.../quote.pdf"
  }
}
Google Sheets

Serves two roles: (1) the master purchase register where every request is logged with a unique request ID, timestamps, and approval status; (2) the spend-threshold rules table that the Approval Routing Agent reads to determine the correct approver. Both live in the same Google Sheets workbook in separate named sheets.

Auth method
OAuth 2.0 using a Google Cloud service account. The service account JSON key is stored in the credential store as GOOGLE_SERVICE_ACCOUNT_JSON. The service account must be granted Editor access to the target workbook. Domain-wide delegation is not required.
Required scopes
https://www.googleapis.com/auth/spreadsheets (read and write to spreadsheet data). No Drive scope is needed if the workbook already exists and the service account has been shared on it directly.
Webhook / trigger setup
Agent 2 (Approval Routing) polls the purchase register sheet for rows where the status column equals 'Pending' and the created_at timestamp is newer than the last poll checkpoint. Poll interval: 60 seconds. The poll checkpoint is stored as a workflow variable, not a credential. No Google Sheets webhook is available natively; polling is the correct mechanism.
Required configuration
Workbook ID stored as GOOGLE_SHEETS_WORKBOOK_ID in the credential store. Purchase register sheet name: 'PurchaseRegister'. Rules table sheet name: 'ApproverRules'. Column structure for PurchaseRegister: request_id, submitted_at, requester_name, requester_email, department, supplier_name, amount_usd, business_justification, document_url, status, approver_email, approver_name, decision_at, po_number, notes. Column structure for ApproverRules: min_amount_usd, max_amount_usd, department, approver_email, approver_name, line_manager_email.
Rate limits
Google Sheets API v4: 300 read requests/minute per project; 60 write requests/minute per user. At 60 submissions/month and a 60-second poll cycle, peak load is well under these limits. No throttling required. Batch writes (appendValues) should be used rather than individual cell updates to minimise quota consumption.
Hard constraints
Never use the first row (header row) for data. Row 1 in each sheet is the frozen header. Request IDs must be sequential integers prefixed 'PR-' (e.g. PR-0001). The rules table must be sorted by min_amount_usd ascending so the routing agent can use a first-match lookup. Empty rows in the rules table will cause the routing logic to fail; validation must be run after any manual edit.
// Write to PurchaseRegister on new Jotform submission
sheets.spreadsheets.values.append(
  spreadsheetId: GOOGLE_SHEETS_WORKBOOK_ID,
  range: 'PurchaseRegister!A:P',
  valueInputOption: 'USER_ENTERED',
  body: { values: [[
    request_id, submitted_at, requester_name, requester_email,
    department, supplier_name, amount_usd, business_justification,
    document_url, 'Pending', '', '', '', '', ''
  ]] }
)

// Read ApproverRules for threshold lookup
sheets.spreadsheets.values.get(
  spreadsheetId: GOOGLE_SHEETS_WORKBOOK_ID,
  range: 'ApproverRules!A:F'
)
Slack

Used by the Approval Routing Agent to deliver a structured Block Kit approval message to the identified approver. The approver clicks an inline Approve or Reject button; Slack posts the interaction payload back to the orchestration layer's interactivity endpoint.

Auth method
OAuth 2.0, Slack app with Bot Token (xoxb-...). The Bot Token is stored as SLACK_BOT_TOKEN. The Signing Secret for request validation is stored as SLACK_SIGNING_SECRET. Both are generated in the Slack app configuration portal at api.slack.com/apps.
Required scopes
chat:write (post messages to channels or DMs), chat:write.public (post to channels without joining), users:read (resolve approver Slack user ID from email), users:read.email (required for email-to-user-ID lookup). Interactive components require no additional scope but the Interactivity Request URL must be configured in the Slack app under Interactivity and Shortcuts.
Webhook / trigger setup
Set the Interactivity Request URL in the Slack app dashboard to the orchestration layer's inbound interaction endpoint. Slack signs every interaction payload with the HMAC-SHA256 signature in the header 'X-Slack-Signature' using a timestamp in 'X-Slack-Request-Timestamp'. The orchestration layer must validate this signature using SLACK_SIGNING_SECRET and reject payloads where the timestamp is older than 300 seconds (replay attack prevention).
Required configuration
The Block Kit message template is stored as a JSON template in the orchestration platform's configuration, not hardcoded per-run. It includes placeholders: {{request_id}}, {{requester_name}}, {{department}}, {{supplier_name}}, {{amount_usd}}, {{business_justification}}, {{document_url}}, {{approve_action_id}}, {{reject_action_id}}. Action IDs stored as SLACK_APPROVE_ACTION_ID and SLACK_REJECT_ACTION_ID in the credential store to allow mapping of interaction payloads back to the correct workflow run.
Rate limits
Slack Web API: Tier 3 methods (chat.postMessage) allow 50+ calls/minute. At 60 requests/month, rate limits pose no risk. No throttling required. The users.lookupByEmail method is Tier 3 (50+/min); also no risk.
Hard constraints
Slack Block Kit buttons must carry a value field containing the request_id so the interaction handler can route the response back to the correct workflow instance without relying on session state. If the approver is not found by email lookup (users.lookupByEmail returns no match), the workflow must fall back to sending a Gmail approval request rather than failing silently. The fallback path must be documented and tested.
// chat.postMessage payload
POST https://slack.com/api/chat.postMessage
Authorization: Bearer <SLACK_BOT_TOKEN>
{
  "channel": "<approver_slack_user_id>",
  "blocks": [
    { "type": "section", "text": { "type": "mrkdwn",
      "text": "*Purchase Request {{request_id}}*\n{{requester_name}} | {{department}}\nSupplier: {{supplier_name}}\nAmount: ${{amount_usd}}\nJustification: {{business_justification}}" } },
    { "type": "actions", "elements": [
      { "type": "button", "text": { "type": "plain_text", "text": "Approve" },
        "style": "primary", "action_id": "<SLACK_APPROVE_ACTION_ID>", "value": "{{request_id}}" },
      { "type": "button", "text": { "type": "plain_text", "text": "Reject" },
        "style": "danger", "action_id": "<SLACK_REJECT_ACTION_ID>", "value": "{{request_id}}" }
    ] }
  ]
}
Integration and API SpecPage 1 of 3
FS-DOC-05Technical
Gmail

Used in three distinct scenarios: (1) Agent 1 sends a resubmission prompt when a Jotform submission has missing required fields; (2) Agent 2 sends a 48-hour escalation email to the approver and their line manager; (3) Agent 3 sends the outcome notification to the requester on approval or rejection.

Auth method
OAuth 2.0. For Google Workspace environments, a service account with domain-wide delegation is preferred so emails are sent from a functional finance address (e.g. finance-approvals@yourcompany.com) rather than an individual user. The service account JSON is shared with the Google Sheets integration (GOOGLE_SERVICE_ACCOUNT_JSON). The impersonation email address is stored as GMAIL_SENDER_ADDRESS.
Required scopes
https://www.googleapis.com/auth/gmail.send (send email on behalf of the impersonated address). No read or modify scope is required; the workflow only sends, never reads inboxes.
Webhook / trigger setup
Gmail is used only as a sender; it does not trigger any workflow step. No webhook or Pub/Sub subscription is needed. All three email sending steps are actions triggered by workflow logic, not by incoming mail events.
Required configuration
Three email templates stored in the orchestration platform configuration: (1) GMAIL_TEMPLATE_RESUBMIT: subject 'Action required: your purchase request is incomplete [{{request_id}}]', body lists missing fields; (2) GMAIL_TEMPLATE_ESCALATION: subject 'Pending approval reminder: {{request_id}} awaiting your decision', copies line_manager_email in CC; (3) GMAIL_TEMPLATE_OUTCOME: subject 'Purchase request {{request_id}} - {{decision}}', body includes PO number for approvals or rejection reason for rejections. All placeholders use {{double_braces}} notation. GMAIL_SENDER_ADDRESS stored in credential store.
Rate limits
Gmail API send quota: 250 quota units/second per user; 1,000,000,000 units/day. Each send costs 100 units. At 60 requests/month generating at most 4 emails per request (resubmit + escalation + outcome + line manager copy), peak is 240 emails/month. No throttling required at current volume.
Hard constraints
Emails must not be sent to the requester until the decision is definitively recorded in Google Sheets. The outcome email must include the request_id in the subject for searchability. The escalation email must CC the line_manager_email from the ApproverRules table, not a hardcoded address. Undeliverable email addresses must trigger an error alert to the workflow admin, not fail silently.
// Gmail API send (escalation example)
POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
Authorization: Bearer <service_account_access_token>
{
  "raw": "<base64url-encoded RFC 2822 message>",
  // Decoded headers:
  // From: finance-approvals@yourcompany.com  (GMAIL_SENDER_ADDRESS)
  // To: <approver_email>
  // CC: <line_manager_email>
  // Subject: Pending approval reminder: PR-0042 awaiting your decision
  // Content-Type: text/html; charset=utf-8
}
Xero

Used exclusively by the PO Creation and Notification Agent to create a draft purchase order on approval of a purchase request. The draft PO is not sent to the supplier by the automation; it requires a finance manager to review and confirm it in the Xero UI before dispatch.

Auth method
OAuth 2.0 with PKCE flow (Xero's standard for web/server apps). The access token and refresh token are stored as XERO_ACCESS_TOKEN and XERO_REFRESH_TOKEN. The tenant ID (organisation) is stored as XERO_TENANT_ID. The client ID and client secret for the Xero app are stored as XERO_CLIENT_ID and XERO_CLIENT_SECRET. Token refresh must be handled automatically by the orchestration layer; Xero access tokens expire after 30 minutes.
Required scopes
accounting.transactions (create and read purchase orders), accounting.contacts.read (look up existing supplier contact records by name). No payroll, payroll.read, files, or assets scopes are required. Scope is declared at app registration in the Xero developer portal.
Webhook / trigger setup
Xero is used only as a write target; it does not trigger any workflow step. No Xero webhook subscription is required for this build. The PO creation step is triggered by the approval decision recorded in Google Sheets, not by any Xero event.
Required configuration
XERO_TENANT_ID stored in credential store. The Xero contact lookup uses the supplier_name from the Jotform submission to search existing contacts via GET /Contacts?searchTerm={{supplier_name}}. If no match is found, the PO is created with the supplier name as a string and flagged in the Google Sheets register under the notes column as 'Xero contact not found - manual match required'. The PO Status is set to 'DRAFT' in all cases. The account code used for line items defaults to the value stored as XERO_DEFAULT_ACCOUNT_CODE (e.g. '500' for general expenses); department-specific overrides can be added to the ApproverRules table as an additional column.
Rate limits
Xero API: 60 API calls/minute; 5,000 calls/day per app per tenant. Each PO creation consumes 2 calls (contact lookup + PO write). At 60 approvals/month (worst case all approved), that is 120 calls/month. No throttling required. If the 60 calls/minute limit is approached during batch processing, implement a 1-second delay between calls.
Hard constraints
PO must always be created in DRAFT status. The automation must never set status to SUBMITTED or AUTHORISED. The finance manager review step is an intentional human control point. The PO number returned by Xero in the response (PurchaseOrderNumber field) must be written back to the purchase register in Google Sheets and included in the requester outcome email. If the Xero API returns an error on PO creation, the workflow must alert the workflow admin via Gmail and record the failure in the Google Sheets register without sending a false confirmation to the requester.
// POST /api.xro/2.0/PurchaseOrders
PUT https://api.xero.com/api.xro/2.0/PurchaseOrders
Authorization: Bearer <XERO_ACCESS_TOKEN>
Xero-Tenant-Id: <XERO_TENANT_ID>
Content-Type: application/json
{
  "PurchaseOrders": [{
    "Contact": { "Name": "{{supplier_name}}" },
    "Date": "{{today_iso8601}}",
    "DeliveryDate": null,
    "Status": "DRAFT",
    "Reference": "{{request_id}}",
    "LineItems": [{
      "Description": "{{business_justification}}",
      "Quantity": 1.0,
      "UnitAmount": {{amount_usd}},
      "AccountCode": "<XERO_DEFAULT_ACCOUNT_CODE>"
    }]
  }]
}

// Expected response field
response.PurchaseOrders[0].PurchaseOrderNumber -> stored as po_number

03Field mappings between tools

The tables below document every field handoff between tools at each agent boundary. All field names are shown in their exact form as they appear in the source or destination system. Mappings must be implemented exactly as specified; any deviation will break the audit trail or cause downstream steps to fail.

Agent 1 handoff: Jotform submission to Google Sheets PurchaseRegister

Source tool
Source field
Destination tool
Destination field
Jotform
`q1_requesterName`
Google Sheets
`requester_name`
Jotform
`q2_requesterEmail`
Google Sheets
`requester_email`
Jotform
`q3_department`
Google Sheets
`department`
Jotform
`q4_supplierName`
Google Sheets
`supplier_name`
Jotform
`q5_amountUsd`
Google Sheets
`amount_usd`
Jotform
`q6_businessJustification`
Google Sheets
`business_justification`
Jotform
`q7_supportingDocument`
Google Sheets
`document_url`
Jotform
`submissionID`
Google Sheets
`jotform_submission_id`
Orchestration layer
`generated_request_id`
Google Sheets
`request_id`
Orchestration layer
`iso8601_timestamp`
Google Sheets
`submitted_at`
Orchestration layer
`literal: Pending`
Google Sheets
`status`

Agent 2 handoff: Google Sheets PurchaseRegister and ApproverRules to Slack Block Kit message

Source tool
Source field
Destination tool
Destination field
Google Sheets
`request_id`
Slack
`{{request_id}}` (Block Kit text + button value)
Google Sheets
`requester_name`
Slack
`{{requester_name}}` (Block Kit section text)
Google Sheets
`department`
Slack
`{{department}}` (Block Kit section text)
Google Sheets
`supplier_name`
Slack
`{{supplier_name}}` (Block Kit section text)
Google Sheets
`amount_usd`
Slack
`{{amount_usd}}` (Block Kit section text)
Google Sheets
`business_justification`
Slack
`{{business_justification}}` (Block Kit section text)
Google Sheets
`document_url`
Slack
`{{document_url}}` (Block Kit accessory link)
ApproverRules
`approver_email`
Orchestration layer
`approver_email` (used for users.lookupByEmail)
Slack API
`users.lookupByEmail.user.id`
Slack
`channel` (DM target for chat.postMessage)

Agent 2 write-back: Slack interaction payload to Google Sheets PurchaseRegister

Source tool
Source field
Destination tool
Destination field
Slack
`payload.actions[0].value`
Google Sheets
`request_id` (used to locate the row)
Slack
`payload.actions[0].action_id`
Orchestration layer
Mapped to `decision` (Approved or Rejected)
Slack
`payload.user.name`
Google Sheets
`approver_name`
Slack
`payload.user.profile.email`
Google Sheets
`approver_email`
Orchestration layer
`iso8601_timestamp`
Google Sheets
`decision_at`
Orchestration layer
`decision`
Google Sheets
`status` (Approved or Rejected)

Agent 3 handoff: Google Sheets PurchaseRegister to Xero PurchaseOrder

Source tool
Source field
Destination tool
Destination field
Google Sheets
`supplier_name`
Xero
`PurchaseOrders[0].Contact.Name`
Google Sheets
`amount_usd`
Xero
`PurchaseOrders[0].LineItems[0].UnitAmount`
Google Sheets
`business_justification`
Xero
`PurchaseOrders[0].LineItems[0].Description`
Google Sheets
`request_id`
Xero
`PurchaseOrders[0].Reference`
Orchestration layer
`today_iso8601`
Xero
`PurchaseOrders[0].Date`
Orchestration layer
`literal: DRAFT`
Xero
`PurchaseOrders[0].Status`
Orchestration layer
`XERO_DEFAULT_ACCOUNT_CODE`
Xero
`PurchaseOrders[0].LineItems[0].AccountCode`
Xero
`PurchaseOrders[0].PurchaseOrderNumber`
Google Sheets
`po_number`
Xero
`PurchaseOrders[0].PurchaseOrderNumber`
Gmail
`{{po_number}}` in outcome email body
Integration and API SpecPage 2 of 3
FS-DOC-05Technical

04Build stack and orchestration

Orchestration layout
Three separate named workflows in the automation platform, one per agent: (1) 'PA-Agent1-RequestIntake', (2) 'PA-Agent2-ApprovalRouting', (3) 'PA-Agent3-POCreationNotification'. All three share a single centralised credential store. No credentials are duplicated across workflows. Each workflow is independently deployable and testable.
Agent 1 trigger mechanism
Webhook (inbound HTTP POST). The orchestration platform exposes a unique inbound webhook URL. This URL is registered in Jotform under Settings > Integrations > Webhooks. On receipt, the workflow validates the HMAC-SHA256 signature (header: x-jotform-signature) using JOTFORM_WEBHOOK_SECRET before processing. The endpoint returns HTTP 200 immediately and processes the payload asynchronously to satisfy Jotform's delivery expectations.
Agent 2 trigger mechanism
Poll (scheduled). The workflow polls the PurchaseRegister sheet every 60 seconds for rows where status equals 'Pending'. It maintains a poll checkpoint timestamp as a workflow variable to avoid reprocessing already-handled rows. A second inbound webhook endpoint is registered in the Slack app as the Interactivity Request URL to receive button interaction payloads. Signature validation uses SLACK_SIGNING_SECRET with timestamp check (reject if older than 300 seconds).
Agent 3 trigger mechanism
Poll (scheduled). The workflow polls the PurchaseRegister sheet every 60 seconds for rows where status equals 'Approved' or 'Rejected' and po_number is empty (for Approved) or outcome_sent is false (for Rejected). This prevents duplicate PO creation or duplicate outcome emails. A boolean flag outcome_sent is written to the register after the Gmail notification is dispatched.
Credential store location
Platform-native encrypted credential vault. All secrets are referenced by key name in workflow configuration. No secret value is written into a workflow node, template, or log.
Logging
Each workflow node writes a structured log entry on success and failure including the request_id, the node name, the timestamp, and the HTTP response code from the external API. Logs are retained for a minimum of 90 days. Errors are forwarded to the admin alert email stored as ADMIN_ALERT_EMAIL.
Deployment environments
Two environments are maintained: 'staging' (connects to Jotform test form, Google Sheets staging workbook, Slack test workspace, Gmail alias, Xero Demo Company) and 'production' (all live credentials). Staging and production credential sets are stored separately in the credential vault with a naming prefix: STAGING_ and PROD_. Promotion from staging to production requires a completed QA sign-off.
Credential store: key names only. Values are never logged or exposed in workflow definitions.
// Credential store contents (production keys)
// All values are secrets; shown here as key names only

JOTFORM_API_KEY                  // Jotform account API key
JOTFORM_FORM_ID                  // Form ID of the purchase request form
JOTFORM_WEBHOOK_SECRET           // HMAC-SHA256 shared secret for signature validation

GOOGLE_SERVICE_ACCOUNT_JSON      // Full service account JSON key (base64 encoded)
GOOGLE_SHEETS_WORKBOOK_ID        // Workbook ID from the Google Sheets URL

SLACK_BOT_TOKEN                  // Bot token (xoxb-...) for chat.postMessage and users.lookupByEmail
SLACK_SIGNING_SECRET             // Signing secret for interaction payload validation
SLACK_APPROVE_ACTION_ID          // Action ID string for the Approve button
SLACK_REJECT_ACTION_ID           // Action ID string for the Reject button

GMAIL_SENDER_ADDRESS             // From address, e.g. finance-approvals@yourcompany.com
GMAIL_TEMPLATE_RESUBMIT          // Template ID or inline template for resubmission prompt
GMAIL_TEMPLATE_ESCALATION        // Template ID or inline template for escalation email
GMAIL_TEMPLATE_OUTCOME           // Template ID or inline template for outcome notification

XERO_CLIENT_ID                   // Xero app client ID
XERO_CLIENT_SECRET               // Xero app client secret
XERO_ACCESS_TOKEN                // Short-lived access token (auto-refreshed, 30 min TTL)
XERO_REFRESH_TOKEN               // Long-lived refresh token for token renewal
XERO_TENANT_ID                   // Organisation (tenant) ID from Xero connections endpoint
XERO_DEFAULT_ACCOUNT_CODE        // Default account code for PO line items (e.g. '500')

ADMIN_ALERT_EMAIL                // Email address to receive workflow error alerts

05Error handling and retry logic

Unhandled exceptions must never fail silently. Every integration point has a defined failure path. If a failure path is not listed in the table below, it is a build defect. All errors must produce a structured log entry and, where flagged, an alert to ADMIN_ALERT_EMAIL.
Integration
Scenario
Required behaviour
Jotform webhook inbound
Signature validation fails (HMAC mismatch or stale timestamp)
Return HTTP 401. Log the rejection with the received signature and request IP. Do not process the payload. Alert ADMIN_ALERT_EMAIL if more than 3 failures occur within 5 minutes (potential replay attack).
Jotform webhook inbound
Required form fields are missing from the submission payload
Return HTTP 200 (acknowledge receipt). Write a partial row to PurchaseRegister with status 'Incomplete'. Send GMAIL_TEMPLATE_RESUBMIT to requester_email. Do not assign a final request_id; use a draft ID prefixed 'DRAFT-'.
Google Sheets write (Agent 1)
Sheets API returns 429 (quota exceeded) or 503 (service unavailable)
Retry up to 4 times with exponential backoff: 2 s, 4 s, 8 s, 16 s. If all retries fail, log the full payload to the orchestration platform's internal error queue and alert ADMIN_ALERT_EMAIL. Do not lose the submission data.
Google Sheets read (Agent 2 poll)
Sheets API returns an error or workbook is unreachable
Skip the poll cycle. Log the error. Retry on the next 60-second cycle. If 5 consecutive poll cycles fail, alert ADMIN_ALERT_EMAIL and pause the workflow until manually resumed.
Slack users.lookupByEmail
Approver email address not found in Slack workspace
Fall back immediately to sending GMAIL_TEMPLATE_ESCALATION directly to approver_email via Gmail. Log the fallback event with the approver email and request_id. Write 'Slack user not found - email fallback used' to the notes column in PurchaseRegister.
Slack chat.postMessage
API returns 4xx (invalid channel, token revoked) or 5xx
Retry once after 10 seconds. If the retry fails, fall back to the Gmail approval request path (send GMAIL_TEMPLATE_ESCALATION to approver_email). Alert ADMIN_ALERT_EMAIL. Log the Slack error code and message.
Slack interaction payload
Interaction payload received with a request_id that does not match any PurchaseRegister row
Return HTTP 200 to Slack (required to prevent Slack retry storms). Log the orphaned payload with the full interaction data. Alert ADMIN_ALERT_EMAIL. Do not write any decision to the register.
48-hour escalation timer (Agent 2)
No Slack response or Gmail reply recorded within 48 hours of approval request dispatch
Trigger escalation: send GMAIL_TEMPLATE_ESCALATION to approver_email and CC line_manager_email from ApproverRules. Update PurchaseRegister notes column: 'Escalation sent at {{timestamp}}'. Continue monitoring for a response; do not close the request automatically.
Xero contact lookup
GET /Contacts returns zero results for the submitted supplier_name
Proceed with PO creation using the supplier_name string as a freetext contact name. Write 'Xero contact not found - manual match required' to the notes column in PurchaseRegister. Do not block the PO creation. Alert ADMIN_ALERT_EMAIL once daily if this condition is present on any row.
Xero PO creation (Agent 3)
Xero API returns 401 (token expired)
Trigger automatic token refresh using XERO_REFRESH_TOKEN and XERO_CLIENT_SECRET. Retry the PO creation immediately after a successful refresh. If the refresh itself fails (refresh token expired or revoked), alert ADMIN_ALERT_EMAIL and pause Agent 3. Do not send an outcome email to the requester until the PO is successfully created.
Xero PO creation (Agent 3)
Xero API returns 400 (validation error, e.g. invalid account code) or 500
Do not retry on 400 (client error; retrying will not resolve it). Log the full Xero error response body. Alert ADMIN_ALERT_EMAIL with the request_id and error detail. Write 'Xero PO creation failed - manual action required' to PurchaseRegister notes. Do not send the requester a confirmation email.
Gmail send (any step)
Gmail API returns 400 (invalid recipient address) or 5xx
Retry on 5xx up to 3 times with 30-second intervals. On 400 (bad address), do not retry; log the invalid address and alert ADMIN_ALERT_EMAIL. For outcome emails specifically, write 'Outcome email delivery failed' to PurchaseRegister notes so finance can follow up manually.
Integration and API SpecPage 3 of 3

More documents for this process

Every document generated for Purchase Approval Workflow.

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