Webhooks and Event-Driven Integration

AGLedger delivers webhook events for mandate lifecycle transitions, agent-to-agent workflows, disputes, and federation activity. Register an HTTPS endpoint, and AGLedger pushes events as they happen.

Register a webhook

curl -X POST "$API_BASE/v1/webhooks" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-system.example.com/webhooks/agledger",
    "eventTypes": ["mandate.created", "mandate.fulfilled"]
  }'
{
  "id": "019d3b10-03cb-...",
  "url": "https://your-system.example.com/webhooks/agledger",
  "eventTypes": ["mandate.created", "mandate.fulfilled"],
  "isActive": true,
  "isPaused": false,
  "format": "standard",
  "secret": "a1b2c3d4...64-char-hex-signing-secret...",
  "secretGraceActive": false,
  "secretGraceExpiresAt": null,
  "circuitState": "closed",
  "consecutiveFailures": 0,
  "lastSuccessfulAt": null,
  "createdAt": "2026-04-09T12:00:00.000Z"
}

Save the secret — it is returned only on creation and rotation. You need it to verify payload authenticity.

Response fields:

Event types

AGLedger supports 28 webhook event types across six categories.

Mandate lifecycle

| Event | When it fires | |-------|--------------| | mandate.created | New mandate created (includes registration and activation) | | mandate.receipt_submitted | Receipt submitted against mandate | | mandate.verification_complete | Verification engine completed check | | mandate.fulfilled | Mandate settled (accountability loop closed) | | mandate.settled | Deprecated alias for mandate.fulfilled — use mandate.fulfilled | | mandate.failed | Mandate failed verification | | mandate.expired | Mandate expired (deadline passed without fulfillment) | | mandate.cancelled | Mandate cancelled |

Agent-to-agent

| Event | When it fires | |-------|--------------| | mandate.proposed | Agent proposed a new mandate to another agent | | mandate.proposal_accepted | Proposed mandate accepted by target agent | | mandate.proposal_rejected | Proposed mandate rejected by target agent | | mandate.delegated | Mandate delegated to a sub-agent | | mandate.revision_requested | Principal requested revision of a receipt |

Settlement and disputes

| Event | When it fires | |-------|--------------| | signal.emitted | Settlement signal emitted | | dispute.opened | Dispute opened against a mandate | | dispute.resolved | Dispute resolved |

Proxy ingestion

| Event | When it fires | |-------|--------------| | proxy.session.synced | Proxy session data synchronized | | proxy.mandate.detected | Mandate detected from proxy session | | proxy.mandate.formalized | Detected mandate formalized into the ledger |

Federation

| Event | When it fires | |-------|--------------| | federation.mandate.offered | Mandate offered to a federated instance | | federation.mandate.accepted | Federated instance accepted a mandate | | federation.mandate.state_changed | Federated mandate changed state | | federation.settlement.signal | Settlement signal received from federated instance | | federation.gateway.registered | Federation gateway registered | | federation.gateway.revoked | Federation gateway access revoked |

Entity references

| Event | When it fires | |-------|--------------| | mandate.reference_added | External reference attached to a mandate | | agent.reference_added | External reference attached to an agent |

Note: mandate.registered and mandate.activated were removed in v0.15.2 (F-329). These internal transitions are now absorbed into the mandate.created event. If you previously subscribed to those types, update your subscriptions to use mandate.created.

Payload format

Each delivery includes the event type, mandate data (ID, status, contract type), timestamp, HMAC signature, and a monotonic eventSequence counter. The sequence counter is per-subscription and strictly increasing — use it to detect out-of-order or duplicate deliveries in HA environments.

Your endpoint must respond within 10 seconds. AGLedger retries 3 times (1s, 10s, 60s backoff) before moving to DLQ.

Verify HMAC signatures

AGLedger signs every payload with HMAC-SHA256. The signature header uses Stripe-style format:

X-Agledger-Signature: t=1774812000,v1=abc123def456...

Python

import hmac, hashlib

def verify_webhook(payload_body: str, signature_header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    timestamp = parts["t"]
    expected_hash = parts["v1"]
    signing_input = f"{timestamp}.{payload_body}"
    computed = hmac.new(secret.encode(), signing_input.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(computed, expected_hash)

JavaScript

import crypto from 'node:crypto';

function verifyWebhook(payloadBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map(p => p.split('=', 2))
  );
  const signingInput = `${parts.t}.${payloadBody}`;
  const computed = crypto
    .createHmac('sha256', secret)
    .update(signingInput)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(computed), Buffer.from(parts.v1)
  );
}

Check delivery status

Poll the delivery log to verify your endpoint received events:

curl "$API_BASE/v1/webhooks/$WEBHOOK_ID/deliveries?limit=50" \
  -H "Authorization: Bearer $API_KEY"

Each delivery record includes:

Manage webhooks

# List all webhooks
curl "$API_BASE/v1/webhooks" -H "Authorization: Bearer $API_KEY"

# Get single webhook
curl "$API_BASE/v1/webhooks/$WEBHOOK_ID" -H "Authorization: Bearer $API_KEY"

# Update webhook (change URL or event types)
curl -X PATCH "$API_BASE/v1/webhooks/$WEBHOOK_ID" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "eventTypes": ["mandate.created", "mandate.fulfilled", "dispute.opened"] }'

# Test connectivity
curl -X POST "$API_BASE/v1/webhooks/$WEBHOOK_ID/ping" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" -d '{}'

# Pause (stop deliveries during maintenance)
curl -X POST "$API_BASE/v1/webhooks/$WEBHOOK_ID/pause" \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" -d '{}'

# Resume
curl -X POST "$API_BASE/v1/webhooks/$WEBHOOK_ID/resume" \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" -d '{}'

# Rotate secret (old secret remains valid during grace period)
curl -X POST "$API_BASE/v1/webhooks/$WEBHOOK_ID/rotate" \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" -d '{}'

# Delete (returns 204)
curl -X DELETE "$API_BASE/v1/webhooks/$WEBHOOK_ID" \
  -H "Authorization: Bearer $API_KEY"

Dead letter queue

Failed deliveries (after 3 retries at 1s, 10s, 60s backoff) go to the DLQ:

# List failed deliveries
curl "$API_BASE/v1/webhooks/$WEBHOOK_ID/dlq" \
  -H "Authorization: Bearer $API_KEY"

# Retry a single delivery
curl -X POST "$API_BASE/v1/webhooks/$WEBHOOK_ID/dlq/$DLQ_ID/retry" \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" -d '{}'

# Retry all
curl -X POST "$API_BASE/v1/webhooks/$WEBHOOK_ID/dlq/retry-all" \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" -d '{}'

Security


Validated against the live API (v0.19.10) on 2026-04-17.