Webhooks and Event-Driven Integration

AGLedger delivers webhook events for mandate lifecycle transitions. 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,
  "secret": "a1b2c3d4...64-char-hex-signing-secret..."
}

Save the secret — you need it to verify payload authenticity.

Event types

| Event | When it fires | |-------|--------------| | mandate.created | New mandate created | | mandate.registered | Enterprise approved | | mandate.activated | Work may begin | | mandate.fulfilled | Accountability loop closed | | mandate.cancelled | Mandate cancelled | | mandate.receipt_submitted | Receipt submitted | | mandate.verification_complete | Verification engine completed |

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)
  );
}

Manage webhooks

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

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

# Pause 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 briefly)
curl -X POST "$API_BASE/v1/webhooks/$WEBHOOK_ID/rotate" \
  -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" -d '{}'

# Delete
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/$ENTRY_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 with 18 assertions against the live API. View test source.