FS-DOC-05Technical
Integration and API Spec
Cybersecurity Incident Response
[YourCompany.com] · IT Department · Prepared by FullSpec · [Today's Date]
This document specifies every API connection, authentication requirement, field mapping, and error-handling behaviour required to build and operate the Cybersecurity Incident Response automation. It covers three agents (Triage Agent, Notification Agent, and Incident Documentation Agent) across six integrated tools: Microsoft Defender, Microsoft 365, Jira, PagerDuty, Slack, and Notion. The FullSpec team uses this spec as the authoritative reference during build and testing. No integration credential, endpoint, or field name in the automation platform should differ from what is recorded here.
01Tool inventory
Tool
Role in automation
Auth method
Min plan required
Used by agent(s)
Orchestration layer
Hosts all workflow logic, credential store, and inter-agent routing
N/A (internal)
N/A
All agents
Microsoft Defender
Source of security alerts; trigger for the entire automation
OAuth 2.0 (client credentials)
Microsoft 365 Business Premium or Defender for Business
Agent 1 (Triage)
Microsoft 365
Audit log and sign-in data retrieval for affected users and devices
OAuth 2.0 (delegated + client credentials)
Microsoft 365 Business Standard or higher
Agent 1 (Triage)
Jira
Incident ticket creation, updates, status tracking, and resolution trigger
OAuth 2.0 (3-legged) or API token (basic auth)
Jira Standard ($10/user/month)
Agent 1 (Triage), Agent 3 (Documentation)
Slack
Incident notifications, escalation messages, and post-incident summaries
OAuth 2.0 (bot token)
Slack Pro or higher
Agent 2 (Notification), Agent 3 (Documentation)
PagerDuty
On-call escalation for Critical and High severity incidents
API key (REST v2) + Events API v2
PagerDuty Professional ($25/user/month)
Agent 2 (Notification)
Notion
Structured incident report page creation and storage
OAuth 2.0 (internal integration token)
Notion Plus ($16/user/month)
Agent 3 (Documentation)
Before you connect anything: all six tool connections must be validated in sandbox or staging environments before any production credentials are entered into the credential store. Use Microsoft Defender's trial tenant, Jira's sandbox project, PagerDuty's test service, and Notion's duplicate workspace to run each auth flow and confirm token acquisition end-to-end. Only after successful sandbox validation should production credentials be loaded.
02Per-tool integration detail
Microsoft Defender
Acts as the automation trigger. The orchestration layer polls the Microsoft Defender API for new alerts on a configurable interval (recommended: 2 minutes) and fires the Triage Agent workflow on each new alert ID not previously processed.
Auth method
OAuth 2.0 client credentials flow. Register an app in Azure Active Directory, assign Defender API permissions, and generate a client secret. Token endpoint: https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
Required scopes
https://api.securitycenter.microsoft.com/.default (application permission: Alert.Read.All, Machine.Read.All, SecurityRecommendation.Read)
Trigger setup
Poll GET https://api.securitycenter.microsoft.com/api/alerts?$filter=createdTime ge {last_poll_timestamp} every 2 minutes. Store last_poll_timestamp in the orchestration state store after each successful run. Deduplicate on alert.id.
Required configuration
Store in credential store: DEFENDER_TENANT_ID, DEFENDER_CLIENT_ID, DEFENDER_CLIENT_SECRET. Do not hardcode. Severity rubric mapping (Critical/High/Medium/Low) stored as a configuration object in the workflow, not inline in code.
Rate limits
Microsoft Defender REST API: 100 calls/minute per tenant. At ~20 alerts/week, peak polling generates fewer than 5 calls/minute. No throttling logic required at current volume, but exponential backoff must be implemented for 429 responses.
Constraints
App registration must be granted admin consent by a Global Administrator in the Microsoft 365 tenant. Token lifetime is 3,600 seconds; the orchestration layer must handle token refresh automatically. Alert data is retained in Defender for 180 days.
// Trigger output (polling response excerpt)
{
"id": "da637574108133732855_-1200514914",
"title": "Suspicious PowerShell execution",
"severity": "High",
"status": "New",
"createdTime": "2025-01-22T09:14:33Z",
"machineId": "device-uuid-001",
"assignedTo": null,
"category": "Execution",
"evidence": [{ "entityType": "Process", "processCommandLine": "powershell.exe -enc ..." }]
}Microsoft 365
Queried by the Triage Agent immediately after alert ingestion to retrieve audit logs and sign-in activity for the affected user account or device identity referenced in the Defender alert.
Auth method
OAuth 2.0 client credentials flow using the same Azure AD app registration as Microsoft Defender (extend permissions on the same app). Token endpoint: https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
Required scopes
https://graph.microsoft.com/.default (application permissions: AuditLog.Read.All, Directory.Read.All, SecurityEvents.Read.All, User.Read.All)
Webhook/trigger setup
Not applicable. Microsoft 365 is queried on demand by the Triage Agent using the affected user UPN or device ID extracted from the Defender alert payload.
Required configuration
Store in credential store: M365_TENANT_ID, M365_CLIENT_ID, M365_CLIENT_SECRET (may share values with Defender app registration if permissions are combined). Audit log query window: 72 hours prior to alert timestamp, stored as a configurable variable AUDIT_LOOKBACK_HOURS.
Rate limits
Microsoft Graph API: 10,000 requests per 10 minutes per app. At ~20 alerts/week, peak load is approximately 2 Graph calls per alert (audit log + sign-in log), well under the limit. No throttling required at current volume.
Constraints
Unified Audit Log must be enabled in the Microsoft 365 tenant (disabled by default in some configurations). The service account or app identity requires the Compliance Administrator or Global Reader role for AuditLog.Read.All. Results are limited to 5,000 records per query; apply date filters to avoid overflow.
// Graph API call: audit log query
GET https://graph.microsoft.com/v1.0/auditLogs/signIns
?$filter=userPrincipalName eq '{upn}' and createdDateTime ge {lookback_start}
&$top=50
// Relevant response fields extracted:
{
"userPrincipalName": "user@example.com",
"ipAddress": "203.0.113.55",
"location": { "city": "Moscow", "countryOrRegion": "RU" },
"riskLevelDuringSignIn": "high",
"status": { "errorCode": 0 },
"createdDateTime": "2025-01-22T08:59:12Z"
}Jira
Used by the Triage Agent to create the incident ticket immediately after severity classification, and by the Incident Documentation Agent to read resolution details when the ticket is closed.
Auth method
Basic auth using an API token (recommended for server-to-server). Base64-encode {email}:{api_token} and pass in Authorization header. Alternatively, OAuth 2.0 (3-legged) for user-context operations. API base: https://{your-domain}.atlassian.net/rest/api/3/
Required scopes
If using OAuth 2.0: read:jira-work, write:jira-work, read:jira-user. If using API token: full project access scoped to the incident tracking project only.
Webhook/trigger setup
Outbound webhook from Jira to the orchestration layer endpoint triggers the Incident Documentation Agent. Configure in Jira: Project Settings > Automation > Webhooks. Event: issue_updated where resolution is set and issue type = Incident. Validate the webhook secret header X-Atlassian-Webhook-UUID against the value stored in JIRA_WEBHOOK_SECRET.
Required configuration
Store in credential store: JIRA_BASE_URL, JIRA_USER_EMAIL, JIRA_API_TOKEN, JIRA_PROJECT_KEY (e.g. IR), JIRA_INCIDENT_ISSUE_TYPE_ID, JIRA_WEBHOOK_SECRET. Severity field: a custom field (customfield_10050) mapped to a single-select with values Critical, High, Medium, Low. Field ID must be confirmed from the target Jira instance and stored as JIRA_SEVERITY_FIELD_ID. Pipeline stages must include: Open, In Progress, Awaiting Review, Resolved, Closed.
Rate limits
Jira Cloud REST API: 10 requests/second per user. At current volume (~20 tickets/week), peak is well under limits. No throttling required. Implement retry with 1-second delay on 429 responses.
Constraints
The project must be pre-configured with the custom severity field and the Incident issue type before build begins. Attachments (log excerpts) are uploaded via POST /rest/api/3/issue/{issueId}/attachments with Content-Type multipart/form-data and the X-Atlassian-Token: no-check header.
// POST /rest/api/3/issue — ticket creation payload
{
"fields": {
"project": { "key": "IR" },
"summary": "[{severity}] {alert_title} - {alert_id}",
"issuetype": { "id": "{JIRA_INCIDENT_ISSUE_TYPE_ID}" },
"description": { "type": "doc", "version": 1, "content": [ ... ] },
"customfield_10050": { "value": "{severity}" },
"labels": ["{alert_category}", "auto-triage"]
}
}Integration and API SpecPage 1 of 3
FS-DOC-05Technical
Slack
Used by the Notification Agent to post formatted incident alerts to the IT incident channel, and by the Incident Documentation Agent to send the post-incident summary once the Jira ticket is resolved.
Auth method
OAuth 2.0 bot token. Install the Slack app to the workspace via the Slack API app dashboard. Store the bot token as SLACK_BOT_TOKEN in the credential store. Bot must be invited to all channels it posts to before go-live.
Required scopes
chat:write, chat:write.public, channels:read, users:read, users:read.email, files:write (if attaching log excerpts as file snippets)
Webhook/trigger setup
Slack is outbound only in this automation (the orchestration layer calls Slack; Slack does not trigger any agent). No inbound webhook or event subscription is required for this workflow. If future interactivity is added (e.g. approve/reject buttons), enable the Interactivity feature and register a Request URL in the Slack app manifest.
Required configuration
Store in credential store: SLACK_BOT_TOKEN, SLACK_IT_INCIDENT_CHANNEL_ID (e.g. C04XXXXXXX), SLACK_CRITICAL_CHANNEL_ID, SLACK_GENERAL_SUMMARY_CHANNEL_ID. Channel IDs must be stored, not channel names, to survive channel renames. Message templates use Block Kit JSON; templates are stored in the workflow configuration, not hardcoded in logic.
Rate limits
Slack Web API (chat.postMessage): Tier 3, 50+ requests/minute. At ~20 alerts/week with 2 Slack messages per alert, peak is under 1 message/minute. No throttling required. Handle 429 responses with the retry_after value from the response header.
Constraints
Bot token must not be a legacy webhook URL. The Slack app must be published to the workspace (not just the developer workspace). Do not use the workspace owner's personal token; use a dedicated bot app.
// chat.postMessage — incident notification payload
{
"channel": "{SLACK_IT_INCIDENT_CHANNEL_ID}",
"blocks": [
{ "type": "header", "text": { "type": "plain_text", "text": "[{severity}] Security Incident Detected" } },
{ "type": "section", "fields": [
{ "type": "mrkdwn", "text": "*Alert:*\n{alert_title}" },
{ "type": "mrkdwn", "text": "*Severity:*\n{severity}" },
{ "type": "mrkdwn", "text": "*Jira Ticket:*\n<{jira_ticket_url}|{jira_ticket_key}>" },
{ "type": "mrkdwn", "text": "*Detected:*\n{alert_timestamp}" }
]}
]
}PagerDuty
Used by the Notification Agent to create an incident and page the on-call engineer when alert severity is classified as Critical or High. Bypasses the standard Slack notification delay for these severity levels.
Auth method
Two API keys required: (1) REST API v2 key for service and incident management (store as PAGERDUTY_API_KEY, passed as Authorization: Token token={key}); (2) Events API v2 integration key for triggering incidents (store as PAGERDUTY_ROUTING_KEY, passed in the request body).
Required scopes
REST API v2: incidents:write, services:read, users:read, escalation_policies:read. Events API v2: no scope model; the routing key is tied to a specific PagerDuty service.
Trigger setup
The automation posts to the PagerDuty Events API v2 endpoint: POST https://events.pagerduty.com/v2/enqueue. This is a one-way push from the orchestration layer. No inbound PagerDuty webhook is needed for the current automation scope. The event payload sets event_action to trigger and references the configured routing key for the on-call incident response service.
Required configuration
Store in credential store: PAGERDUTY_API_KEY, PAGERDUTY_ROUTING_KEY, PAGERDUTY_SERVICE_ID. A PagerDuty service named 'Cybersecurity Incident Response' must be pre-created with an escalation policy covering the IT Manager and senior stakeholders. The service's integration type must be set to 'Events API v2'. Dedup key format: {jira_ticket_key}-{alert_id} to prevent duplicate pages for the same incident.
Rate limits
PagerDuty Events API v2: no published hard rate limit for normal volumes. REST API v2: 960 requests/minute. At ~4 Critical/High alerts per week, no throttling is required. Implement retry with exponential backoff on 429 or 5xx responses.
Constraints
Only alerts classified as Critical or High by the Triage Agent trigger PagerDuty. Medium and Low severity alerts route to Slack only. The dedup_key must be consistent for the same incident to prevent multiple pages if the workflow retries. Acknowledge and resolve actions in PagerDuty are out of scope for this build; those remain manual.
// POST https://events.pagerduty.com/v2/enqueue
{
"routing_key": "{PAGERDUTY_ROUTING_KEY}",
"event_action": "trigger",
"dedup_key": "{jira_ticket_key}-{alert_id}",
"payload": {
"summary": "[{severity}] {alert_title} - Action required",
"source": "Microsoft Defender",
"severity": "{pagerduty_severity}",
"timestamp": "{alert_timestamp}",
"custom_details": {
"jira_ticket": "{jira_ticket_url}",
"affected_asset": "{machine_id}",
"alert_category": "{alert_category}"
}
}
}Notion
Used by the Incident Documentation Agent to create a structured incident report page in a designated Notion database when a Jira ticket is resolved, and to store the page URL for inclusion in the post-incident Slack summary.
Auth method
OAuth 2.0 internal integration token. Create an internal integration in the Notion integration settings, grant it access to the target database, and store the token as NOTION_API_TOKEN. Pass as Authorization: Bearer {NOTION_API_TOKEN}. API version header required: Notion-Version: 2022-06-28.
Required scopes
Internal integration capabilities required: Read content, Insert content, Update content. The integration must be explicitly shared with the target database page in Notion (database is not accessible by default even with a valid token).
Webhook/trigger setup
Not applicable. Notion does not provide native outbound webhooks. The Incident Documentation Agent is triggered by the Jira webhook (issue resolved event), not by Notion. The agent then calls the Notion API to create the report page.
Required configuration
Store in credential store: NOTION_API_TOKEN, NOTION_INCIDENT_DATABASE_ID. The Notion incident report database must be pre-created with the following property schema before build: Name (title), Severity (select: Critical/High/Medium/Low), Status (select: Open/In Progress/Resolved), Jira Ticket (url), Alert ID (rich_text), Affected Asset (rich_text), Detection Time (date), Resolution Time (date), Summary (rich_text), Log Excerpt (rich_text), Actions Taken (rich_text), Reported By (rich_text). Database ID is extracted from the database URL and stored as NOTION_INCIDENT_DATABASE_ID.
Rate limits
Notion API: 3 requests/second average; burst to higher rates may result in 429 responses. At ~20 incidents/week with 1-2 API calls per incident, peak is under 0.1 requests/second. No throttling required. Implement retry with the retry-after header value on 429 responses.
Constraints
Notion API does not support file attachments natively. Log excerpts are inserted as rich_text blocks within the page body, not as attached files. Page content is limited to 2,000 blocks; log excerpts must be truncated or summarised if they exceed this. The integration token does not expire but should be rotated on a 90-day schedule.
// POST https://api.notion.com/v1/pages — incident report creation
{
"parent": { "database_id": "{NOTION_INCIDENT_DATABASE_ID}" },
"properties": {
"Name": { "title": [{ "text": { "content": "[{severity}] {alert_title} - {jira_ticket_key}" } }] },
"Severity": { "select": { "name": "{severity}" } },
"Status": { "select": { "name": "Resolved" } },
"Jira Ticket": { "url": "{jira_ticket_url}" },
"Alert ID": { "rich_text": [{ "text": { "content": "{alert_id}" } }] },
"Affected Asset": { "rich_text": [{ "text": { "content": "{machine_id}" } }] },
"Detection Time": { "date": { "start": "{alert_timestamp}" } },
"Resolution Time": { "date": { "start": "{resolved_timestamp}" } }
},
"children": [
{ "object": "block", "type": "heading_2", "heading_2": { "rich_text": [{ "text": { "content": "Log Excerpt" } }] } },
{ "object": "block", "type": "paragraph", "paragraph": { "rich_text": [{ "text": { "content": "{log_excerpt_truncated}" } }] } }
]
}03Field mappings between tools
The tables below document every field passed between tools at each agent handoff. Use exact field names as shown. Any rename or transformation is noted in the Destination field column.
Handoff 1: Microsoft Defender to Jira (Triage Agent creates incident ticket)
Source tool
Source field
Destination tool
Destination field
Microsoft Defender
`alert.id`
Jira
`summary` (prefixed: [severity] title - alert_id)
Microsoft Defender
`alert.title`
Jira
`summary` (see above) + `description.content`
Microsoft Defender
`alert.severity`
Jira
`customfield_10050.value` (mapped: High=High, Medium=Medium, etc.)
Microsoft Defender
`alert.category`
Jira
`labels[]` (appended alongside 'auto-triage')
Microsoft Defender
`alert.machineId`
Jira
`description.content` (Affected Asset field)
Microsoft Defender
`alert.createdTime`
Jira
`description.content` (Detection Time field)
Microsoft Defender
`alert.evidence[].processCommandLine`
Jira
`description.content` (Evidence block)
Microsoft 365 (Graph)
`signIn.ipAddress`
Jira
`description.content` (Sign-in IP field)
Microsoft 365 (Graph)
`signIn.location.countryOrRegion`
Jira
`description.content` (Sign-in Location field)
Microsoft 365 (Graph)
`signIn.riskLevelDuringSignIn`
Jira
`description.content` (Risk Level field)
Microsoft 365 (Graph)
`auditLog.activityDisplayName`
Jira
`description.content` (Last Audit Activity field)
Orchestration layer (computed)
`severity_rubric_score`
Jira
`customfield_10050.value` (overrides raw Defender severity if rubric differs)
Handoff 2: Jira to Slack and PagerDuty (Notification Agent routes and escalates)
Source tool
Source field
Destination tool
Destination field
Jira
`issue.key`
Slack
`blocks[].fields[].text` (Jira Ticket field in Block Kit)
Jira
`issue.key`
PagerDuty
`dedup_key` (prefixed: issue.key-alert_id)
Jira
`issue.fields.summary`
Slack
`blocks[].header.text`
Jira
`issue.fields.summary`
PagerDuty
`payload.summary`
Jira
`issue.fields.customfield_10050.value`
Slack
`blocks[].fields[].text` (Severity field)
Jira
`issue.fields.customfield_10050.value`
PagerDuty
`payload.severity` (mapped: Critical=critical, High=error, Medium=warning, Low=info)
Jira
`issue.self`
Slack
`blocks[].fields[].text` (URL for Jira Ticket link)
Jira
`issue.fields.created`
Slack
`blocks[].fields[].text` (Detected timestamp field)
Jira
`issue.fields.customfield_10050.value`
Orchestration layer (router)
`routing_decision` (Critical/High = PagerDuty + Slack; Medium/Low = Slack only)
Handoff 3: Jira to Notion and Slack (Incident Documentation Agent compiles report and sends summary)
Source tool
Source field
Destination tool
Destination field
Jira
`issue.key`
Notion
`properties['Jira Ticket'].url`
Jira
`issue.fields.summary`
Notion
`properties['Name'].title[].text.content`
Jira
`issue.fields.customfield_10050.value`
Notion
`properties['Severity'].select.name`
Jira
`issue.fields.created`
Notion
`properties['Detection Time'].date.start`
Jira
`issue.fields.resolutiondate`
Notion
`properties['Resolution Time'].date.start`
Jira
`issue.fields.description` (log excerpt block)
Notion
`children[].paragraph.rich_text[].text.content` (Log Excerpt)
Jira
`issue.fields.comment.comments[-1].body`
Notion
`children[].paragraph.rich_text[].text.content` (Actions Taken)
Jira
`issue.fields.assignee.displayName`
Notion
`properties['Reported By'].rich_text[].text.content`
Notion (computed)
`page.url`
Slack
`blocks[].fields[].text` (Incident Report link in post-incident summary)
Jira
`issue.key`
Slack
`blocks[].fields[].text` (Jira Ticket reference in post-incident summary)
Jira
`issue.fields.resolution.name`
Slack
`blocks[].fields[].text` (Resolution field in post-incident summary)
Integration and API SpecPage 2 of 3
FS-DOC-05Technical
04Build stack and orchestration
Orchestration layout
One workflow per agent: three discrete workflows named Triage-Agent-Workflow, Notification-Agent-Workflow, and Incident-Documentation-Agent-Workflow. Workflows share a single credential store and a shared state store for deduplication keys and last_poll_timestamp. Inter-agent communication is event-driven: each workflow is triggered either by an external system event (Defender poll, Jira webhook) or by the output event emitted by the preceding workflow.
Triage Agent trigger mechanism
Poll. The orchestration layer polls the Microsoft Defender REST API every 2 minutes using a scheduled trigger. Filtered by createdTime >= last_poll_timestamp. The last_poll_timestamp is updated in the shared state store after each successful poll cycle. Processed alert IDs are written to a deduplication set to prevent reprocessing on overlapping poll windows.
Notification Agent trigger mechanism
Event-driven (internal). Triggered by the Triage Agent emitting an 'alert_triaged' event upon successful Jira ticket creation. The event payload carries jira_ticket_key, severity, alert_title, jira_ticket_url, and alert_id. No inbound webhook from external systems.
Incident Documentation Agent trigger mechanism
Webhook. Triggered by an inbound HTTP POST from Jira's automation webhook when an issue with issue type Incident transitions to status Resolved. Signature validation: the orchestration layer verifies the X-Atlassian-Webhook-UUID header value matches JIRA_WEBHOOK_SECRET stored in the credential store. Requests failing signature validation are rejected with HTTP 401 and logged.
Shared credential store
All secrets are stored in the orchestration platform's encrypted credential store. No credential is hardcoded in any workflow step, expression, or configuration file. Credentials are referenced by key name only (see code block below). Credential store access is restricted to the automation platform service account.
State store
The orchestration layer maintains a lightweight key-value state store for: last_poll_timestamp (updated per Defender poll cycle); processed_alert_ids (set with 24-hour TTL for deduplication); active_incident_keys (set of open Jira ticket keys to prevent duplicate documentation runs).
All values are injected at runtime from the encrypted credential store. Never commit actual secrets to version control or workflow configuration files.
// Credential store contents — reference keys only
// Microsoft Defender + Microsoft 365 (shared Azure AD app)
DEFENDER_TENANT_ID = "<azure-tenant-id>"
DEFENDER_CLIENT_ID = "<azure-app-client-id>"
DEFENDER_CLIENT_SECRET = "<azure-app-client-secret>"
M365_TENANT_ID = "<azure-tenant-id>" // same value as DEFENDER_TENANT_ID
M365_CLIENT_ID = "<azure-app-client-id>" // same app registration
M365_CLIENT_SECRET = "<azure-app-client-secret>" // same secret
// Jira
JIRA_BASE_URL = "https://<your-domain>.atlassian.net"
JIRA_USER_EMAIL = "<service-account-email>"
JIRA_API_TOKEN = "<jira-api-token>"
JIRA_PROJECT_KEY = "IR"
JIRA_INCIDENT_ISSUE_TYPE_ID = "<issue-type-id-from-jira-meta>"
JIRA_SEVERITY_FIELD_ID = "customfield_10050"
JIRA_WEBHOOK_SECRET = "<uuid-from-jira-webhook-config>"
// Slack
SLACK_BOT_TOKEN = "xoxb-<token>"
SLACK_IT_INCIDENT_CHANNEL_ID = "C04XXXXXXX"
SLACK_CRITICAL_CHANNEL_ID = "C04YYYYYYY"
SLACK_GENERAL_SUMMARY_CHANNEL_ID = "C04ZZZZZZZ"
// PagerDuty
PAGERDUTY_API_KEY = "<rest-api-v2-key>"
PAGERDUTY_ROUTING_KEY = "<events-api-v2-integration-key>"
PAGERDUTY_SERVICE_ID = "<pagerduty-service-id>"
// Notion
NOTION_API_TOKEN = "secret_<internal-integration-token>"
NOTION_INCIDENT_DATABASE_ID = "<32-char-database-id>"
// Orchestration state store keys (managed internally, not user-configured)
// last_poll_timestamp — ISO 8601, updated after each Defender poll
// processed_alert_ids — set<string>, TTL 86400s
// active_incident_keys — set<string>, cleared on Jira ticket close
The Microsoft Defender and Microsoft 365 integrations share the same Azure AD app registration. Confirm with the tenant Global Administrator that both sets of API permissions (SecurityCenter and Graph) are granted admin consent on the same app before the Triage Agent build begins. This is the most common cause of auth failures during integration testing.
05Error handling and retry logic
Every integration point has a defined failure behaviour. Unhandled exceptions must never fail silently. All errors must be written to the orchestration platform's run log with the integration name, error code, payload summary, and timestamp. The table below defines required behaviour for each failure scenario.
Integration
Scenario
Required behaviour
Microsoft Defender (poll)
HTTP 401 Unauthorized (expired or invalid token)
Trigger immediate token refresh using client credentials flow. Retry the poll once after token acquisition. If token refresh fails, send a Slack alert to SLACK_CRITICAL_CHANNEL_ID with error detail and halt polling until resolved. Do not fail silently.
Microsoft Defender (poll)
HTTP 429 Too Many Requests
Read Retry-After header value. Pause polling for the specified duration. Retry once after the wait period. Log the throttle event. No alert required unless 429s persist for more than 10 minutes.
Microsoft Defender (poll)
HTTP 5xx or connection timeout
Retry with exponential backoff: 30s, 60s, 120s. After 3 failed retries, post a Slack error alert to SLACK_CRITICAL_CHANNEL_ID and suspend polling. Resume on next scheduled cycle after 10 minutes.
Microsoft 365 (Graph query)
HTTP 403 Forbidden (insufficient permissions)
Do not retry. Log the error with the affected UPN and alert ID. Create the Jira ticket with all available Defender data and flag the log retrieval failure in the ticket description. Post a warning to SLACK_IT_INCIDENT_CHANNEL_ID. Escalate to FullSpec at support@gofullspec.com if persistent.
Microsoft 365 (Graph query)
Audit log returns zero results (log not yet available)
Retry after 60 seconds, then 120 seconds. If still empty after 2 retries, proceed with Jira ticket creation without log data and set a flag field 'Log Retrieval: Pending' in the ticket description. Do not block the triage flow.
Jira (ticket creation)
HTTP 400 Bad Request (invalid field value or missing required field)
Do not retry. Log the full request payload and error response. Post an error message to SLACK_CRITICAL_CHANNEL_ID with the alert ID and field validation error. The IT Manager must create the ticket manually from the Slack alert detail. This must not fail silently.
Jira (ticket creation)
HTTP 429 or 5xx
Retry with exponential backoff: 15s, 30s, 60s. After 3 retries, post an error alert to SLACK_CRITICAL_CHANNEL_ID with full alert data so the IT Manager can act. Log all retry attempts with timestamps.
Jira (webhook inbound)
Webhook signature validation fails (invalid JIRA_WEBHOOK_SECRET)
Reject the request with HTTP 401. Log the source IP and payload hash. Do not trigger the Incident Documentation Agent. Post a security alert to SLACK_CRITICAL_CHANNEL_ID. Investigate before re-enabling the webhook endpoint.
Slack (message post)
HTTP 404 channel_not_found (channel ID invalid or bot not invited)
Do not retry. Log the error with the channel ID and message payload. Attempt to post to SLACK_CRITICAL_CHANNEL_ID as fallback. If that also fails, write the full message content to the orchestration run log for manual recovery. Alert must not be lost.
PagerDuty (event trigger)
HTTP 400 Bad Request (invalid routing key or payload)
Do not retry. Log the full payload and error. Immediately attempt a fallback Slack message to SLACK_CRITICAL_CHANNEL_ID with the full incident detail so the on-call engineer is notified manually. This is a critical path for Critical/High incidents and must not fail silently.
PagerDuty (event trigger)
HTTP 429 or 5xx
Retry with exponential backoff: 10s, 30s, 90s. After 3 retries, execute the Slack fallback notification described above. Log all retry attempts.
Notion (page creation)
HTTP 404 object_not_found (database ID invalid or integration not shared)
Do not retry. Log the error with NOTION_INCIDENT_DATABASE_ID. Post an error to SLACK_IT_INCIDENT_CHANNEL_ID instructing the IT Manager to create the report manually using the Jira ticket data. Provide the Jira ticket URL in the error message. Contact support@gofullspec.com if the database ID has not changed.
Notion (page creation)
HTTP 429 Too Many Requests
Read retry-after header. Wait and retry once. If second attempt fails, log and post a Slack warning. The post-incident summary Slack message still fires, with a note that the Notion page creation is pending manual follow-up.
The PagerDuty and Slack notification paths for Critical and High severity incidents are the highest-priority failure points in this automation. If both PagerDuty and the fallback Slack message fail for a Critical alert, the orchestration platform must write the full incident payload to a persistent error queue and send an email alert to the IT Manager's registered email address as a last-resort fallback. This must be configured as part of the build, not treated as an edge case.
Integration and API SpecPage 3 of 3