Chapter 35 · Volume IV — Runtime Subsystems

External Integrations

35.1 Overview

ADL provides declarative integrations for communicating with external systems. Integrations are event-driven: when a history event fires, a workflow step completes, or a manual trigger executes, the integration dispatches an API call, processes a webhook, or publishes to a message queue.

Each integration declares the protocol (REST, webhook, S3, SMTP, AMQP, MQTT), authentication, data mapping, error handling, retry policy, and resilience patterns. The runtime manages token refresh, circuit breakers, rate limiting, dead letter queues, and logging.


35.2 Integration Types

REST (Outbound HTTP)

Calls external REST APIs:

integrations:
  grantsGovSubmission:
    description: "Submit proposal to Grants.gov"
    type: rest
    baseUrl: "https://api.grants.gov/v2"
    
    auth:
      type: api_key
      header: X-API-Key
      value: "${GRANTS_GOV_API_KEY}"
    
    operations:
      submitProposal:
        trigger:
          resource: Proposal
          event: transition
          transition: submitToSponsor
        
        request:
          method: POST
          path: "/submissions"
          headers:
            Content-Type: "application/json"
          body:
            opportunity_id: $record.fundingOpportunityId
            project:
              title: $record.title
              abstract: $record.abstractText
            pi:
              first_name: $record.piFirstName
              last_name: $record.piLastName
              email: $record.piEmail
        
        onSuccess:
          action: update
          resource: Proposal
          target: $record.id
          input:
            submissionId: $response.submission_id
            submittedAt: $now()
          history:
            event: "proposalSubmitted"
            label: "Proposal submitted — ID {submissionId}"
        
        onError:
          action: transition
          resource: Proposal
          target: $record.id
          transition: submissionFailed

Webhook (Inbound HTTP)

Receives events from external systems:

integrations:
  sponsorNotifications:
    description: "Receive award notifications from sponsor"
    type: webhook
    path: /webhooks/sponsor-notifications
    
    auth:
      type: hmac_signature
      secret: "${WEBHOOK_SECRET}"
      header: X-Webhook-Signature
      algorithm: sha256
    
    events:
      awardNotification:
        filter:
          field: "event_type"
          value: "award_notification"
        
        mapping:
          awardNumber: $payload.award_id
          sponsorAwardId: $payload.sponsor_reference
          awardedAmount: $payload.total_amount
          startDate: "$parseDate($payload.period_start, 'yyyy-MM-dd')"
          endDate: "$parseDate($payload.period_end, 'yyyy-MM-dd')"
        
        action: create
        resource: Award
        input: $mapped
        
        history:
          event: "awardReceived"
          label: "Award received from {sponsor} — {awardNumber}"

S3 (Object Storage)

AWS S3-compatible storage for files:

integrations:
  documentStorage:
    type: s3
    bucket: "${S3_BUCKET}"
    region: "${AWS_REGION}"
    
    auth:
      type: aws_sigv4
      accessKeyId: "${AWS_ACCESS_KEY_ID}"
      secretAccessKey: "${AWS_SECRET_ACCESS_KEY}"

Operations: upload, download, delete, presigned URL generation.

SMTP (Email)

Direct email sending:

integrations:
  emailServer:
    type: smtp
    host: "${SMTP_HOST}"
    port: 587
    
    auth:
      type: basic
      username: "${SMTP_USER}"
      password: "${SMTP_PASS}"

AMQP (Message Queue)

RabbitMQ and AMQP-compatible brokers:

integrations:
  instrumentDataQueue:
    type: amqp
    url: "${AMQP_URL}"
    queue: "instrument.results"
    
    credentials:
      username: "${AMQP_USER}"
      password: "${AMQP_PASS}"
    
    events:
      resultReceived:
        mapping:
          sampleId: $message.sample_accession
          rawResult: $message.result_value
          resultUnit: $message.result_unit
        
        action: update
        resource: Sample
        target: "$findOne(Sample, accessionNumber == $mapped.sampleId).id"
        input:
          rawResult: $mapped.rawResult
          resultUnit: $mapped.resultUnit

MQTT (IoT)

Lightweight messaging for sensors:

integrations:
  sensorNetwork:
    type: mqtt
    broker: "${MQTT_BROKER_URL}"
    topics:
      - "sensors/+/temperature"
      - "sensors/+/pressure"

35.3 Authentication Methods

TypeUse Case
api_keyAPI key in header or query parameter
bearerStatic bearer token
oauth2_client_credentialsOAuth 2.0 with automatic token refresh
oauth2_authorization_codeOAuth 2.0 for user-delegated access
basicBasic HTTP authentication
hmac_signatureHMAC signature verification (webhooks)
mtlsMutual TLS with client certificates
ip_whitelistIP-based access control (webhooks)
aws_sigv4AWS Signature V4 for AWS services

OAuth 2.0 Example

auth:
  type: oauth2_client_credentials
  tokenUrl: "https://auth.service.com/token"
  clientId: "${OAUTH_CLIENT_ID}"
  clientSecret: "${OAUTH_CLIENT_SECRET}"
  scopes: ["read", "write"]
  tokenCache: true

The runtime caches the access token, refreshes it before expiry, and retries requests with a new token if the original returns 401.


35.4 Data Mapping

Outbound Mapping (Domain → External)

body:
  opportunity_id: $record.fundingOpportunityId
  applicant:
    duns: "${INSTITUTION_DUNS}"
    ein: "${INSTITUTION_EIN}"
    name: "${INSTITUTION_NAME}"
  project:
    title: $record.title
    abstract: $record.abstractText
    start_date: "$formatDate($record.proposedStartDate, 'yyyy-MM-dd')"
  budget:
    total_requested: $record.requestedAmount

Nested objects are supported. AEL expressions transform values. Environment variables inject deployment-specific values.

Inbound Mapping (External → Domain)

mapping:
  awardNumber: $payload.award_id
  sponsorAwardId: $payload.sponsor_reference
  awardedAmount: $payload.total_amount
  startDate: "$parseDate($payload.period_start, 'yyyy-MM-dd')"
  endDate: "$parseDate($payload.period_end, 'yyyy-MM-dd')"

$payload is the parsed incoming message. $message is used for queue-based integrations. $mapped references the completed mapping for use in actions.

Transform Functions

FunctionDescription
$formatDate(value, pattern)Format date to string
$parseDate(string, pattern)Parse string to date
$toUpper(string)Uppercase
$toLower(string)Lowercase
$trim(string)Trim whitespace
$substring(string, start, len)Extract substring
$replace(string, find, replace)String replacement
$split(string, delimiter)Split to array
$join(array, delimiter)Join array to string
$toNumber(string)Parse numeric string
$coalesce(a, b, c)First non-null value
$findOne(Resource, filter)Query single record
$findMany(Resource, filter)Query multiple records
$now()Current timestamp
$uuid()Generate UUID

35.5 Resilience Patterns

Retry with Exponential Backoff

retry:
  maxAttempts: 3
  backoff: exponential
  baseDelay: 1s
  maxDelay: 30s
  retryOn: [500, 502, 503, 504, 429]

Retry sequence: 1s → 2s → 4s (exponential) or 1s → 1s → 1s (fixed).

Circuit Breaker

circuitBreaker:
  enabled: true
  failureThreshold: 5
  successThreshold: 2
  timeout: 60s

States: closed (normal) → open (failing) → half_open (testing) → closed

When open, requests fail immediately without hitting the external service.

Rate Limiting

rateLimit:
  requestsPerSecond: 10
  burstSize: 20
  onExceeded: queue
StrategyBehavior
queueQueue requests until capacity available
rejectReturn 429 immediately
backoffDelay before retrying

Dead Letter Queue

deadLetter:
  enabled: true
  maxRetries: 3
  notifyOn: [integration_failure]

Failed messages (after all retries) are stored in the dead letter queue with:

  • Original payload
  • Error details
  • Attempt count
  • Timestamps

Manual retry:

POST /api/v1/{domain}/integrations/{name}/deadletter/{id}/retry

35.6 Webhook Security

HMAC Signature Verification

auth:
  type: hmac_signature
  secret: "${WEBHOOK_SECRET}"
  header: X-Webhook-Signature
  algorithm: sha256
  encoding: hex
  prefix: "sha256="

The runtime computes HMAC-SHA256(secret, request_body) and compares it to the header value. Mismatches are rejected.

Replay Protection

replayProtection:
  enabled: true
  timestampHeader: X-Webhook-Timestamp
  maxAge: 5 minutes
  nonceHeader: X-Webhook-Nonce

Rejects messages older than maxAge and tracks nonces to prevent duplicate delivery.

IP Whitelist

auth:
  type: ip_whitelist
  allowed:
    - "52.1.0.0/16"
    - "203.0.113.0/24"

35.7 Integration Logging

All integration operations are logged to {domain}_integration_log:

CREATE TABLE {domain}_integration_log (
    id              TEXT PRIMARY KEY,
    integration     TEXT NOT NULL,
    operation       TEXT NOT NULL,
    direction       TEXT NOT NULL,     -- outbound, inbound
    method          TEXT,
    url             TEXT,
    requestBody     TEXT,
    responseStatus  INTEGER,
    responseBody    TEXT,
    durationMs      INTEGER,
    success         BOOLEAN NOT NULL,
    errorMessage    TEXT,
    attemptNumber   INTEGER,
    triggeredBy     TEXT,
    createdAt       TEXT NOT NULL
);

Sensitive data (API keys, tokens, passwords) is automatically redacted from logs.


35.8 Integration APIs

List Integrations

GET /api/v1/{domain}/integrations

Get Integration Status

GET /api/v1/{domain}/integrations/{name}/status

Returns circuit breaker state, rate limiter metrics, recent success/failure rate.

Manual Trigger

POST /api/v1/{domain}/integrations/{name}/operations/{operation}
{
  "recordId": "proposal-uuid",
  "dryRun": false
}

Dry run mode shows the request payload without sending it.

Dead Letter Queue

GET /api/v1/{domain}/integrations/{name}/deadletter
    ?status=pending
    
POST /api/v1/{domain}/integrations/{name}/deadletter/{id}/retry

Integration Health

GET /api/v1/{domain}/integrations/{name}/health

Returns:

  • Circuit breaker state
  • Recent error rate
  • Average response time
  • Rate limiter status
  • Dead letter queue size

35.9 Use Cases

ScenarioIntegration Type
Submit proposal to Grants.govREST outbound with OAuth 2.0
Receive award notificationsWebhook inbound with HMAC verification
Pull credit reportsREST outbound with API key
Store documents in S3S3 with AWS Signature V4
Send email notificationsSMTP with basic auth
Receive lab instrument resultsAMQP message queue
Monitor IoT sensorsMQTT with topic subscriptions
File drug safety reports to FDAREST outbound with mutual TLS
Sync with external databaseDatabase connector

35.10 Integration Points

FeatureIntegration
History (§21)Event triggers fire integration operations; operations produce history events
Workflows (§25)Workflow steps can trigger integration operations; integrations can start workflows
Notifications (§23)Integrations use SMTP for email delivery
Templates (§27)Document generation can trigger S3 upload
Schedules (§24)Scheduled tasks can trigger integration operations
Rules (§22)Rule violations can trigger external notifications

End of Chapter 35.

End of Volume IV — Runtime Subsystems.

Next: Volume V — Reference

Chapter 36: AEL (ADL Expression Language)