Skip to main content

Documentation Index

Fetch the complete documentation index at: https://archie.com/docs/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks deliver real-time HTTP notifications to your own services whenever data changes in a project. When a record is inserted, updated, or deleted on any Data Model table, Archie sends a signed POST to a URL you control, with the event payload in the body. This page covers webhooks Archie sends from your project to external systems. For incoming webhook handling on the integration side (Stripe events, GitHub events, etc.), see Integrations → Webhooks.

How it works

  1. Register a webhook subscription: which table, which events, which URL.
  2. Data changes — every matching mutation (REST or GraphQL) publishes an event.
  3. Delivery — the webhook service POSTs to your URL with the payload.
  4. Verify — your endpoint validates the HMAC signature, then processes the event.
  5. Retry — non-2xx responses retry with exponential backoff up to 5 attempts.

Webhook management API

All management endpoints live under /api/rest/_webhooks and require a management API token.
MethodEndpointPurpose
POST/api/rest/_webhooksCreate a subscription
GET/api/rest/_webhooksList subscriptions
GET/api/rest/_webhooks/{id}Get subscription + recent deliveries
PATCH/api/rest/_webhooks/{id}Update a subscription
DELETE/api/rest/_webhooks/{id}Delete a subscription

Required headers

HeaderRequiredDescription
AuthorizationYesBearer <management-token>
X-Project-IDYesProject identifier
environmentYesTarget environment — must match the body for create/update
The environment header is required on every webhook management request, including GET and DELETE.

Create a webhook

curl -X POST "https://your-gateway.example.com/gw/api/rest/_webhooks" \
  -H "Authorization: Bearer your-management-token" \
  -H "X-Project-ID: your-project-id" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "your-project-id",
    "environment": "master",
    "table": "orders",
    "events": ["INSERT", "UPDATE", "DELETE"],
    "url": "https://your-app.example.com/webhooks/orders",
    "secret": "whsec_your_signing_secret",
    "active": true
  }'
FieldTypeRequiredDescription
projectIdStringYesProject identifier.
environmentStringYesTarget environment.
tableStringYesTable name to watch.
eventsArrayYesAny of INSERT, UPDATE, DELETE.
urlStringYesHTTPS endpoint to receive events.
secretStringYesHMAC signing secret — keep this safe.
activeBooleanNoDefault true. Set false to pause delivery.
filterObjectNoOnly deliver for records matching this filter.
selectArrayNoLimit which fields are included in the payload.

Filtering events

Only deliver when the record matches a condition:
{
  "table": "orders",
  "events": ["UPDATE"],
  "url": "https://your-app.example.com/webhooks/completed-orders",
  "secret": "whsec_secret",
  "filter": { "status": "completed" }
}

Selecting fields

Limit the payload to specific fields — useful for keeping payloads small or avoiding sensitive data in webhook bodies:
{
  "table": "orders",
  "events": ["INSERT"],
  "url": "https://your-app.example.com/webhooks/new-orders",
  "secret": "whsec_secret",
  "select": ["id", "total", "status", "customerId"]
}

Event payload

When a matching change happens, Archie delivers an HTTP POST to your URL with this body:
{
  "id": "evt_abc123def456",
  "timestamp": "2025-12-15T15:00:00Z",
  "project_id": "your-project-id",
  "environment": "master",
  "table": "orders",
  "event": "INSERT",
  "record": {
    "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "total": 99.50,
    "status": "pending",
    "customerId": "cust-001"
  },
  "data": {
    "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "total": 99.50,
    "status": "pending",
    "customerId": "cust-001"
  },
  "changed_fields": ["total", "status"]
}
FieldDescription
idUnique event identifier — use this to deduplicate.
timestampISO 8601 timestamp of the event.
project_idProject that generated the event.
environmentEnvironment in which the change happened.
tableTable that was modified.
eventINSERT, UPDATE, or DELETE.
recordFull record (or selected fields) after the mutation.
dataSame as record, kept for compatibility.
changed_fieldsFields that changed (for UPDATE; empty for INSERT and DELETE).
For DELETE, record reflects the row’s state immediately before deletion.

Delivery headers

Each delivery includes:
HeaderValue
Content-Typeapplication/json
X-Archie-EventINSERT / UPDATE / DELETE
X-Archie-DeliveryThe event id (for tracking and dedup).
X-Archie-Signaturesha256=<hex> HMAC signature with prefix.
X-Webhook-SignatureRaw hex digest, no prefix.

Verifying signatures

Always verify the HMAC signature before processing the payload — never trust an unsigned webhook body.

Node.js

const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

  const sig = signature.replace('sha256=', '');
  return crypto.timingSafeEqual(
    Buffer.from(sig, 'hex'),
    Buffer.from(expected, 'hex')
  );
}

app.post('/webhooks/orders', (req, res) => {
  const signature = req.headers['x-archie-signature'];
  const isValid = verifyWebhookSignature(
    JSON.stringify(req.body),
    signature,
    process.env.WEBHOOK_SECRET
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const { event, table, record } = req.body;
  // process the event...
  res.status(200).send('OK');
});

Python

import hmac
import hashlib

def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()
    sig = signature.replace('sha256=', '')
    return hmac.compare_digest(sig, expected)
Always use a constant-time comparison (timingSafeEqual, hmac.compare_digest) to avoid leaking the signature one byte at a time.

Retries and circuit breakers

If your endpoint returns a non-2xx status or doesn’t respond, the webhook service retries with exponential backoff:
AttemptDelay
1Immediate
2~1 second
3~5 seconds
4~30 seconds
5~5 minutes
After the maximum attempts, the delivery moves to a dead-letter state and shows up in GET /_webhooks/{id} for inspection. A circuit breaker protects your endpoint from sustained failures: after 5 consecutive failures, deliveries pause for a 2-minute cooldown, then retry. This avoids hammering an endpoint that’s clearly down.

Inspecting deliveries

GET /api/rest/_webhooks/{id} returns the subscription and recent delivery history:
{
  "webhook": {
    "id": "wh_a1b2c3d4-e5f6-7890",
    "table": "orders",
    "events": ["INSERT", "UPDATE", "DELETE"],
    "url": "https://your-app.example.com/webhooks/orders",
    "active": true,
    "createdAt": "2025-12-15T14:30:00Z"
  },
  "deliveries": [
    {
      "id": "del_x1y2z3",
      "eventId": "evt_abc123",
      "status": "delivered",
      "httpStatus": 200,
      "attempts": 1,
      "createdAt": "2025-12-15T15:00:00Z"
    }
  ]
}
Use this endpoint as part of your monitoring — alert if a webhook accumulates failed deliveries.

Best practices

  • Always verify signatures. Never trust an unsigned payload.
  • Respond fast. Return 200 within 5 seconds. Process the event asynchronously if needed.
  • Deduplicate by event id. The same event may be redelivered if your endpoint returns a non-2xx — process the work once.
  • Use HTTPS. Webhook URLs must be HTTPS.
  • Monitor for dead-letter deliveries. Check /_webhooks/{id} regularly or wire it to alerting.

FAQ

Use webhooks for server-to-server delivery — your backend reacts to data changes from another machine. Use GraphQL subscriptions for client UIs — a browser stays connected over a WebSocket and receives events while it’s open. Webhooks retry; subscriptions don’t.
Network failures or 5xx responses trigger a retry. The retry uses the same event id (X-Archie-Delivery), so you can deduplicate by tracking processed event ids on your side. At-least-once delivery is the trade-off for guaranteed delivery on transient failures.
Yes — PATCH the subscription with a new secret. To rotate without downtime, configure your endpoint to accept either the old or new secret during the rotation window, then drop the old one once the new is in place.
Deliveries retry up to 5 times over a few minutes, then move to dead-letter. They aren’t auto-redelivered after that — you’d need to read the dead-letter list and replay them yourself. For at-least-once-with-long-windows guarantees, plan endpoint reliability accordingly.
Yes. Multiple subscriptions to the same table-and-event combination each receive a copy of the event. Useful for sending the same event to different downstream systems.