Integration Guide

This document explains how external systems integrate with Xema Workflows, including webhook triggers, trigger payloads, external actions, and best practices.

Table of Contents

  1. Overview
  2. Single Ingress Edge Architecture
  3. Webhook Triggers
  4. Supported Providers
  5. Trigger Payloads
  6. Workflow Dispatch API
  7. External Actions
  8. Integration Patterns
  9. Security & Authentication
  10. Idempotency & Retry

Overview

How Integration Works

External systems trigger and interact with Xema Workflows through:

  1. Webhook triggers — GitHub, GitLab, Jira, Slack, custom webhooks
  2. API dispatch — Manual workflow triggers via REST API
  3. Schedule triggers — Cron-based automation
  4. Workflow calls — Workflows calling other workflows

Architecture Principle

Single Ingress Edge: All external provider webhooks flow through a single normalization layer (connector-gateway-api), which standardizes events into canonical envelope format before routing to domain services.

This ensures:

  • ✅ Consistent webhook handling across providers
  • ✅ Single security boundary
  • ✅ Deterministic idempotency keys
  • ✅ No direct webhook access to domain services

Single Ingress Edge Architecture

Flow Diagram

text
1External Provider          Integration Adapter        Workflow Engine
2(GitHub, GitLab,    →      (Normalize Event)    →    (Trigger Workflow)
3 Jira, Slack, ...)
4                              ↓
5                    Canonical Envelope
6                    (provider, event, payload)
7                              ↓
8                    Idempotency Check
9                    (provider:deliveryId)
10                              ↓
11                    POST /webhooks/provider-data
12                    (Domain Service)
13                              ↓
14                    Workflow Match & Trigger
15                    (Filter by projectId, branch, etc.)

Benefits

  1. Normalization — All providers speak the same language
  2. Idempotency — Deterministic keys prevent duplicate execution
  3. Security — Single validation point
  4. Extensibility — Add new providers without touching domain services
  5. Auditability — Complete webhook history

Webhook Triggers

Declaring Webhook Triggers

Workflows declare which events they respond to:

yaml
1on:
2  webhook:
3    - event: scm.push
4      filters:
5        projectId: proj_123
6        branch: main
7    - event: scm.pull_request
8      filters:
9        projectId: proj_123
10        state: opened
11    - event: tracker.issue
12      filters:
13        projectId: proj_123
14        type: feature
15    - event: tracker.issue
16      filters:
17        projectId: proj_123
18        type: bug
19        priority: high

Event Filters

Webhooks are only triggered if all specified filters match:

yaml
1filters:
2  projectId: proj_123           # Project ID
3  branch: main                  # Branch name (SCM only)
4  state: opened                 # State (PR/issue)
5  type: feature                 # Issue type
6  priority: high                # Priority level

Accessing Webhook Data

In workflow jobs, access the incoming webhook:

yaml
1jobs:
2  process-push:
3    uses: xema/agent
4    with:
5      provider: ${{ trigger.provider }}        # e.g., "github"
6      event: ${{ trigger.event }}              # e.g., "scm.push"
7      repo: ${{ trigger.payload.repo.name }}
8      branch: ${{ trigger.payload.branch }}
9      commit_sha: ${{ trigger.payload.commit.sha }}
10      commit_message: ${{ trigger.payload.commit.message }}
11      author: ${{ trigger.payload.commit.author }}

Supported Providers

SCM Providers

GitHub

Events:

  • scm.push — Code pushed to repository
  • scm.pull_request — Pull request opened/updated/closed
  • scm.repository — Repository created/deleted
  • scm.installation — GitHub App installed/uninstalled

Payload:

json
1{
2  "provider": "GITHUB",
3  "adapterKey": "SCM",
4  "event": "scm.push",
5  "payload": {
6    "repo": {
7      "name": "myrepo",
8      "owner": "acme",
9      "url": "https://github.com/acme/myrepo",
10      "isPrivate": false
11    },
12    "branch": "main",
13    "before": "abc123...",
14    "after": "def456...",
15    "commits": [...],
16    "pusher": "alice"
17  }
18}

GitLab

Connector biome @xemahq/biomes-platform-gitlab. Covers GitLab-hosted repositories and their issues.

Events (canonical names the connector emits):

  • scm.push.received — a push. The payload carries the repository clone URL and full name, ref, branch (empty for non-branch refs), beforeSha, afterSha, commitCount and commits.
  • scm.change_request.opened / .updated / .merged / .closed — merge request events. The payload carries the title, description, state, source and target branch, head SHA and merge commit SHA when present.
  • tracker.item.created — an issue is opened. tracker.item.updated — an issue is closed, reopened or updated.
  • tracker.comment.added — a comment on an issue. Comments on merge requests, commits and snippets are not forwarded.

Other GitLab event kinds are received and dropped without starting a run.

Verification: GitLab sends the webhook secret you configure in the X-Gitlab-Token header; Xema compares it. There is no body signature. X-Gitlab-Event-UUID is the delivery id used for idempotency.

Install: the connector uses oauth_user with the read_api and read_repository scopes. An admin supplies the GitLab OAuth application ID and secret (an org can override the platform app). When installing the connector, pick the connected GitLab instance and at least one repository (project).

Actions: the connector also exposes repository operations as capabilities: connector:scm.get-repository@1, list-branches, list-files, get-file, get-diff, list-commits, get-checks, list-pull-requests, get-pull-request and list-pull-request-comments (all low risk), and create-branch, create-pull-request, update-pull-request and post-pull-request-comment, which are high risk and require approval before they run.

Gitea

Connector biome @xemahq/biomes-platform-gitea, for self-hosted Gitea instances.

Events:

  • scm.push.received — a push (same payload shape as GitLab above).
  • scm.change_request.opened / .updated / .merged / .closed — pull request events. A pull request whose state is merged is reported as merged; an opened action as opened; closed as closed; any other action as updated.

Other Gitea events are received and dropped. Issues and comments are not ingested.

Verification: Gitea signs the body with HMAC-SHA256 and sends the raw hex digest (no sha256= prefix) in X-Gitea-Signature. The event kind comes from X-Gitea-Event, and X-Gitea-Delivery is the delivery id.

Install: credential kind pat. An admin supplies the instance base URL (no trailing slash) and a personal access token with repo and write:repository scope. When installing the connector, pick the connected Gitea instance and at least one repository.

Azure DevOps

Connector biome @xemahq/biomes-platform-azure-devops, for Azure DevOps Services and Server.

Events (from service hooks):

  • scm.push.received — git.push (same payload shape as GitLab above).
  • scm.change_request.opened — git.pullrequest.created; scm.change_request.merged — git.pullrequest.merged; for git.pullrequest.updated the result is merged, closed or updated depending on the pull request state.

Other service-hook event types are received and dropped.

Verification: the service hook uses HTTP Basic authentication. Xema compares the Authorization header with the credential pair stored on the connection, so configure the hook with the same username and token.

Install: credential kind basic. An admin supplies the organization URL (for example https://dev.azure.com/your-organization), a username, and a personal access token with at least Code (Read) scope. When installing the connector, pick the connected organization and at least one repository.

Issue Tracker Providers

Jira

Events:

  • tracker.issue — Issue created/updated/transitioned

Payload:

json
1{
2  "provider": "ATLASSIAN",
3  "adapterKey": "TRACKER",
4  "event": "tracker.issue",
5  "payload": {
6    "issue": {
7      "key": "PROJ-123",
8      "title": "Add user authentication",
9      "description": "...",
10      "type": "feature",
11      "status": "in-progress",
12      "priority": "high",
13      "assignee": "[email protected]",
14      "reporter": "[email protected]",
15      "labels": ["backend", "urgent"]
16    }
17  }
18}

Confluence

Events:

  • doc.page.created, doc.page.updated, doc.page.deleted — page events. Confluence page_created maps to created; page_trashed and page_removed to deleted; page_updated, page_restored and any other page or blog event to updated.
  • doc.space.created, doc.space.updated — space events.

Atlassian connector setup

Jira and Confluence share one connector biome, @xemahq/biomes-platform-atlassian (connector key ATLASSIAN), and one webhook URL. Confluence pages and spaces are delivered as documentation events and Jira issues and comments as tracker events.

Jira events (beyond the issue event above): tracker.item.created, tracker.item.updated, tracker.item.deleted for jira:issue_created, jira:issue_updated and jira:issue_deleted, and tracker.comment.added for comment events. Other event names are received and dropped.

Verification: Atlassian signs the body with HMAC-SHA256 and sends X-Hub-Signature: sha256=<hex>. Atlassian sends no delivery id, so Xema derives the idempotency key from a hash of the body.

Install: credential kind basic. An admin supplies the Atlassian account email and an API token (created under Security, API tokens in the Atlassian account settings). When installing the connector, pick the connected Atlassian Cloud site and optionally restrict it to named Confluence space keys and Jira project keys.

Linear

Connector biome @xemahq/biomes-platform-linear, for Linear workspaces.

Events:

  • tracker.issue.created — an issue is created.
  • tracker.issue.updated — an issue is updated.

The payload carries the Linear issue id, its identifier (for example ENG-123), the title and the workflow state name. Linear comment events and other entity types are received and dropped, and Linear does not emit a separate "transitioned" event: a state change arrives as tracker.issue.updated, and the new state is in the payload.

Verification: Linear signs each delivery and sends the signature in the linear-signature header. The linear-delivery header is the delivery id.

Install: credential kind oauth_user with read and write scopes. An admin supplies the Linear OAuth client ID and secret. When installing the connector, pick the connected workspace and at least one Linear project, and optionally limit it to named state transitions (the default is any).

Actions: the connector declares a create-issue-comment action (inputs issueId and body). In the current release it is a reference skeleton that does not yet write a comment in Linear, so do not rely on it in production workflows.

Communication Providers

Slack

Connector biome @xemahq/biomes-platform-slack. It feeds Slack messages into conversation channels, where agent sessions answer them, and delivers the replies back.

Events:

  • channel.message.received — a new message.
  • channel.message.edited — an edited message.

The payload carries senderId, conversationId (the Slack channel), text, externalMessageId (the Slack timestamp) and, for thread replies, threadId. Reactions, channel joins, deleted messages, bot messages and other event types are received and dropped.

json
1{
2  "provider": "SLACK",
3  "event": "channel.message.received",
4  "payload": {
5    "senderId": "U12345678",
6    "conversationId": "C12345678",
7    "text": "Hey, start a workflow",
8    "externalMessageId": "1234567890.123456"
9  }
10}

Verification: Slack signs each request with the app's signing secret (X-Slack-Signature with X-Slack-Request-Timestamp). A request whose timestamp is more than 5 minutes off is rejected. Slack's one-time URL verification handshake is answered by the gateway and does not start a run.

Outbound: connector:channel.send-message@1 with conversationId and block posts one reply to the channel.

Install: credential kind bot_token, with the channels:history, groups:history, im:history and chat:write scopes. An admin supplies the Slack app's client ID, client secret and signing secret, and installs the app to the workspace through Slack's OAuth flow. When installing the connector, pick the connected Slack workspace.


Trigger Payloads

Canonical Envelope Format

All webhooks are normalized to this format:

The base class is ConnectorWebhookEnvelopeDto<TPayload> in @xemahq/platform-common. Each owning domain service declares one concrete subclass per WebhookEntityKind.

typescript
1class ConnectorWebhookEnvelopeDto<TPayload = unknown> {
2  provider: string
3    // Curated providers use the ConnectorKind vocabulary — GITHUB, GITLAB,
4    // GITEA, XEMA_GITEA, AZURE_DEVOPS, ATLASSIAN, SLACK, TELEGRAM, ...
5    // The column is open so an installed biome can contribute its own.
6
7  adapterKey: AdapterKey
8    // SCM | TRACKER | DOCUMENTATION | ...
9
10  event: string
11    // Canonical event name (e.g. "scm.push")
12
13  entityKind: WebhookEntityKind
14    // Discriminator: scm.push, scm.pull_request, tracker.issue, ...
15
16  deliveryId: string
17    // Unique per webhook delivery — the idempotency input
18
19  occurredAt: string
20  correlationId: string
21  externalId?: string
22  externalSpaceKey?: string
23  connectionId?: string
24    // Which named connection this delivery arrived on
25
26  payload: TPayload
27    // Typed per entityKind
28}

Idempotency Key

The workflow engine computes:

Idempotency-Key = {provider}:{deliveryId}

This ensures:

  • Each webhook is processed exactly once
  • Duplicate deliveries are detected and skipped
  • Safe retry without side effects

Payload Examples

GitHub Push Event

yaml
1on:
2  webhook:
3    - event: scm.push
4
5jobs:
6  on-push:
7    uses: xema/agent
8    with:
9      repo_url: ${{ trigger.payload.repo.url }}
10      branch: ${{ trigger.payload.branch }}
11      commits: ${{ trigger.payload.commits }}
12      pusher: ${{ trigger.payload.pusher }}

Jira Issue Event

yaml
1on:
2  webhook:
3    - event: tracker.issue
4      filters:
5        type: feature
6
7jobs:
8  on-issue:
9    uses: xema/agent
10    with:
11      issue_key: ${{ trigger.payload.issue.key }}
12      title: ${{ trigger.payload.issue.title }}
13      priority: ${{ trigger.payload.issue.priority }}
14      assignee: ${{ trigger.payload.issue.assignee }}

Workflow Dispatch API

Manual Trigger via REST API

Trigger a workflow manually:

bash
1curl -X POST https://workflow-engine-api.xema.dev/workflows/{slug}/dispatch \
2  -H "Authorization: Bearer $TOKEN" \
3  -H "Content-Type: application/json" \
4  -d '{
5    "workflowId": "wf_123",
6    "inputs": {
7      "project_name": "Acme Portal",
8      "scope": "full",
9      "budget": 500000
10    }
11  }'

Response

json
1{
2  "runId": "run_abc123",
3  "status": "pending",
4  "createdAt": "2026-04-27T14:30:00Z"
5}

Polling for Results

bash
1curl -X GET https://workflow-engine-api.xema.dev/runs/run_abc123 \
2  -H "Authorization: Bearer $TOKEN"

Response:

json
1{
2  "runId": "run_abc123",
3  "workflowId": "wf_123",
4  "status": "running",
5  "jobs": [
6    {
7      "jobId": "job_1",
8      "status": "running"
9    }
10  ],
11  "startedAt": "2026-04-27T14:30:05Z"
12}

External Actions

Custom Webhooks

Workflows can send webhooks to external systems:

yaml
1jobs:
2  send-notification:
3    needs: analysis
4    uses: xema/webhook
5    with:
6      url: https://hooks.acme.com/deployments
7      method: POST
8      headers:
9        Authorization: "Bearer ${{ secrets.WEBHOOK_TOKEN }}"
10        Content-Type: application/json
11      payload:
12        status: ${{ needs.analysis.outputs.status }}
13        result: ${{ needs.analysis.outputs.result }}

HTTP Requests

Make arbitrary HTTP calls:

yaml
1jobs:
2  call-external-api:
3    uses: xema/http
4    with:
5      url: https://api.external.com/process
6      method: POST
7      headers:
8        Authorization: "Bearer ${{ secrets.API_KEY }}"
9      body:
10        data: ${{ needs.previous.outputs.data }}
11    outputs:
12      response: ${{ result.body }}

Integration with External Tools

Example: Trigger AWS Lambda

yaml
1jobs:
2  trigger-lambda:
3    uses: xema/http
4    with:
5      url: https://lambda.amazonaws.com/invoke
6      method: POST
7      headers:
8        Authorization: "Bearer ${{ secrets.AWS_TOKEN }}"
9      body:
10        functionName: my-function
11        payload:
12          input: ${{ inputs.data }}

Example: Post to Slack

yaml
1jobs:
2  notify-slack:
3    uses: xema/webhook
4    with:
5      url: ${{ secrets.SLACK_WEBHOOK_URL }}
6      payload:
7        text: "Workflow completed: ${{ needs.final.outputs.status }}"
8        uploads:
9          - title: "Summary"
10            text: ${{ needs.final.outputs.summary }}

Integration Patterns

Pattern 1: Git-Based Triggers

Trigger workflows on code changes:

yaml
1on:
2  webhook:
3    - event: scm.push
4      filters:
5        branch: main
6
7jobs:
8  build:
9    uses: xema/agent
10    with:
11      task: Build and test
12      repo_url: ${{ trigger.payload.repo.url }}
13      branch: ${{ trigger.payload.branch }}
14      commit_sha: ${{ trigger.payload.commit.sha }}

Use case: CI/CD pipeline on every push

Pattern 2: Issue-Driven Workflows

Trigger workflows from issue trackers:

yaml
1on:
2  webhook:
3    - event: tracker.issue
4      filters:
5        type: feature
6        priority: high
7
8jobs:
9  plan-feature:
10    uses: xema/agent
11    with:
12      task: Create feature delivery plan
13      issue_key: ${{ trigger.payload.issue.key }}
14      title: ${{ trigger.payload.issue.title }}
15      requirements: ${{ trigger.payload.issue.description }}

Use case: Automated planning from feature requests

Pattern 3: Multi-Tool Orchestration

Coordinate workflows across multiple external systems:

yaml
1on:
2  webhook:
3    - event: scm.pull_request
4      filters:
5        state: opened
6
7jobs:
8  review:
9    uses: xema/agent
10    with:
11      pr_url: ${{ trigger.payload.pull_request.url }}
12      pr_number: ${{ trigger.payload.pull_request.number }}
13      code_diff: ${{ trigger.payload.pull_request.diff }}
14    outputs:
15      review: ${{ result.review }}
16
17  post-comment:
18    needs: review
19    uses: xema/webhook
20    with:
21      url: ${{ trigger.payload.pull_request.comments_url }}
22      method: POST
23      body:
24        body: ${{ needs.review.outputs.review }}
25
26  update-status:
27    needs: review
28    uses: xema/http
29    with:
30      url: ${{ trigger.payload.pull_request.status_url }}
31      method: POST
32      body:
33        state: "success"
34        description: ${{ needs.review.outputs.status }}

Security & Authentication

API Tokens

All API calls require authentication:

bash
1curl -H "Authorization: Bearer $XEMA_TOKEN" \
2  https://workflow-engine-api.xema.dev/workflows/...

Webhook Verification

External providers send signed webhooks to prevent spoofing:

Header: X-Signature-256 = HMAC-SHA256(secret, body)

Xema verifies the signature before processing.

Secrets in Workflows

Reference secrets securely:

yaml
1jobs:
2  deploy:
3    uses: xema/http
4    with:
5      url: https://api.deployment.com/deploy
6      headers:
7        Authorization: "Bearer ${{ secrets.DEPLOYMENT_TOKEN }}"

Secrets are:

  • ✅ Encrypted at rest
  • ✅ Never logged
  • ✅ Audited for access
  • ✅ Rotatable

Permission Scopes

Workflows declare access requirements:

yaml
1permissions:
2  repos: read              # Read SCM
3  kb: limited             # Limited KB access
4  backlog: write          # Full issue tracking
5  integrations: read      # Read integrations

Idempotency & Retry

Idempotent Webhook Processing

Each webhook has a deterministic idempotency key:

Key = {provider}:{deliveryId}

This ensures:

  • First delivery: Processed normally
  • Duplicate deliveries: Skipped (already processed)
  • Failed deliveries: Retried with same key

Workflow Retry Policy

Configure how failed workflows are retried:

yaml
1defaults:
2  retry:
3    maxAttempts: 3
4    backoffCoefficient: 2.0
5    initialInterval: 1s
6    maxInterval: 60s

Example: Safe External API Calls

yaml
1jobs:
2  call-external:
3    retry:
4      maxAttempts: 5
5    uses: xema/http
6    with:
7      url: https://api.external.com/process
8      body:
9        idempotency_key: ${{ trigger.payload.deliveryId }}

External API also receives idempotency key, enabling end-to-end safety.


Integration Checklist

When integrating an external system:

  • Declare webhook events in on.webhook
  • Add appropriate filters to avoid unwanted triggers
  • Access payload via ${{ trigger.payload.* }}
  • Use ${{ secrets.* }} for authentication
  • Include idempotency keys in external calls
  • Handle timeouts gracefully (retry policy)
  • Test with webhook simulation tools
  • Monitor webhook delivery in audit logs
  • Document expected behavior and filters
  • Set up alerts for failed workflows

Examples

Example 1: GitHub → Code Review

yaml
1name: automatic-code-review
2on:
3  webhook:
4    - event: scm.pull_request
5      filters:
6        state: opened
7
8jobs:
9  analyze:
10    uses: xema/agent
11    with:
12      task: Review pull request code for quality and security
13      repo_url: ${{ trigger.payload.repo.url }}
14      pr_number: ${{ trigger.payload.pull_request.number }}
15      code_diff: ${{ trigger.payload.pull_request.diff }}
16    outputs:
17      review_comment: ${{ result.review }}
18
19  post-review:
20    needs: analyze
21    uses: xema/webhook
22    with:
23      url: ${{ trigger.payload.pull_request.comments_url }}
24      method: POST
25      body:
26        body: ${{ needs.analyze.outputs.review_comment }}

Example 2: Jira → Feature Specification

yaml
1name: jira-to-spec
2on:
3  webhook:
4    - event: tracker.issue
5      filters:
6        type: feature
7
8jobs:
9  generate-spec:
10    uses: xema/agent
11    with:
12      task: Create detailed feature specification
13      issue_title: ${{ trigger.payload.issue.title }}
14      requirements: ${{ trigger.payload.issue.description }}
15      priority: ${{ trigger.payload.issue.priority }}
16    outputs:
17      spec_id: ${{ result.artifact_id }}
18
19  notify:
20    needs: generate-spec
21    uses: xema/webhook
22    with:
23      url: https://notifications.acme.com/webhook
24      payload:
25        issue_key: ${{ trigger.payload.issue.key }}
26        spec_generated: true
27        spec_id: ${{ needs.generate-spec.outputs.spec_id }}

Next: Read Expressions & Data Flow for details on data passing between jobs.