Webhook ingest

Many services already speak HTTPS webhooks. Azure DevOps Service Hooks, Azure Event Grid system topics, GitHub push webhooks — they POST a JSON body to a URL when something happens. ActivityLog accepts these natively.

The endpoint is:

POST https://api.activitylog.com/api/v1/webhooks/{alw_TOKEN}

where {alw_TOKEN} is a webhook URL token that you mint in the portal (note the alw_ prefix vs al_ for native ingest).

Generate a webhook URL

  1. Sidebar → Systems → open the system that should receive the activity (or + Mint new system first).
  2. In the Webhook endpoint panel, optionally enter a signing secret (see Signature checking), then click Generate webhook URL.
  3. The full URL is shown once in a dialog. Copy it.

The same panel can Rotate the URL (the old one stops working) or Revoke it.

The full URL to give your source system is:

https://api.activitylog.com/api/v1/webhooks/alw_REPLACE_WITH_YOUR_TOKEN

Webhook URL tokens are URL-safe — your source system pastes them as part of the URL, not as a header. The token still gives only ingest access; loss has the same blast radius as a regular al_ token.

What the endpoint accepts

Three shapes, picked by request body, all on the same URL:

Source Body shape Detected by
Azure Event Grid validation handshake JSON array, first element has eventType: "Microsoft.EventGrid.SubscriptionValidationEvent" body content
CloudEvents v1.0 preflight OPTIONS request with WebHook-Request-Origin header HTTP method
Anything else — single object or array JSON object or JSON array body content

You don't need to tell us which is which — the dispatcher figures it out.

Azure Event Grid

Subscription validation (one-time, automatic)

When Event Grid first registers a subscription against your webhook URL, it POSTs a validation envelope. Our endpoint detects and answers it transparently — no work on your end. No Message row is created for the validation event.

Steady-state events

After validation, Event Grid POSTs event batches:

POST /api/v1/webhooks/alw_REPLACE_WITH_YOUR_TOKEN
Content-Type: application/json
aeg-event-type: Notification

[
  { "id": "...", "eventType": "Microsoft.Resources.ResourceWriteSuccess", ... },
  { "id": "...", "eventType": "Microsoft.Resources.ResourceDeleteSuccess", ... }
]

Response (HTTP 202 Accepted):

{ "messageIds": ["mid_...", "mid_..."] }

Each event becomes one Message row. The Azure resource id, operation name, actor, and full event payload are stored (subject to your tier — see Free-tier auto-projection below).

Provision an Event Grid system topic

Walk through this in your Azure portal:

  1. Event Grid → System Topics → Create.
  2. Topic type: pick Microsoft.Resources.Subscriptions for subscription-level CUD, or any other system topic you want to forward.
  3. Source: the Azure subscription you want events from.
  4. Resource group + name: any.
  5. After creation, open the topic and + Event Subscription.
  6. Endpoint type: Webhook.
  7. Endpoint: paste https://api.activitylog.com/api/v1/webhooks/alw_REPLACE_WITH_YOUR_TOKEN.
  8. Create.

Event Grid sends the validation event, our endpoint echoes the validation code, and you're live.

For the full deep-dive on Event Grid handshake semantics and the data model, see ../Event-Grid-Ingest.md.

Azure DevOps Service Hooks

In your ADO organization:

  1. Project Settings → Service hooks → + Create subscription.
  2. Service: Web Hooks.
  3. Trigger: pick an event type (Code pushed, Pull request created, Work item updated, Build completed, etc.).
  4. Action:
    • URL: https://api.activitylog.com/api/v1/webhooks/alw_REPLACE_WITH_YOUR_TOKEN
    • Basic authentication: leave blank.
    • Resource details to send: All.
    • Messages to send: All.
    • Detailed messages to send: All.
  5. Test — should respond with HTTP 201/202.
  6. Finish.

Each ADO event becomes one message with type = webhook.ado.<eventType> (webhook.ado.git.push, webhook.ado.git.pullrequest.created, webhook.ado.build.complete and so on). For pull requests, pushes, builds and release deployments the API also adds change-control fields (change.kind, pull_request, push, build, deploy, repo) and the approval state change.state; see 71-integrations-azure-devops.md.

Repeat per event type you want to capture; ADO requires one subscription per event class.

See 71-integrations-azure-devops.md for the recommended event-type set and a complete walkthrough.

GitHub webhooks

In your GitHub repository:

  1. Settings → Webhooks → Add webhook.
  2. Payload URL: https://api.activitylog.com/api/v1/webhooks/alw_REPLACE_WITH_YOUR_TOKEN
  3. Content type: application/json.
  4. Secret: the signing secret you entered when you generated the URL (or leave blank if you did not set one).
  5. Which events: pick what you want. Push events + Pull request + Workflow runs is a sensible starter set.
  6. Active: ✓.
  7. Add webhook.

GitHub pings the URL on save, and the ping is stored like any other delivery. Each delivery becomes one message with type = webhook.github.<X-GitHub-Event> (for example webhook.github.push), and the X-GitHub-Delivery id is used as its idempotency key.

Signature checking

If you set a signing secret when you generated the URL, ActivityLog checks the X-Hub-Signature-256: sha256=<hex> header (GitHub's format: HMAC-SHA256 of the raw request body with your secret) and rejects a mismatch with 401 webhook-signature-invalid. A request that carries no signature header is still accepted, so the URL itself remains the secret. Azure DevOps service hooks and Event Grid do not sign requests this way; leave the secret empty for them.

Free-tier auto-projection

Webhook payloads can be large (ADO PRs often ship 18 KB+ of curated JSON). On the Free tier, the API automatically projects each webhook event down to a lean ~1 KB metadata-only envelope:

  • event_type — the source event type
  • actor_id / actor_email — who did it
  • target_id / target_url — what it acted on
  • summary — first 200 chars of the source's summary field

The full payload is discarded. This is the only way the Free tier accepts webhook ingest — without projection, a single ADO event would blow the 16 KB body cap.

Pro and Enterprise tenants keep the full source-curated payload (Mode 2 storage — see ../../../ActivityLog-Integrations/Common-Architecture.md § 4).

Upgrade impact: When you upgrade Free → Pro, new webhook events arrive with full payloads. Existing rows stay at lean projection — no backfill.

Response codes

HTTP Meaning
200 Validation handshake (Event Grid / CloudEvents preflight) acknowledged
200 A single delivery whose idempotency key was already stored — body is { "deduplicated": true }
201 Single delivery stored — body is { "id": "...", "ingestedAt": "..." }
202 Array stored — body is { "messageIds": ["...", ...] }
401 webhook-signature-invalid — the signature header did not match your signing secret
403 webhook-disabled — the system is disabled
404 The URL token is wrong / revoked / never existed
413 Payload too large (no enforced cap today; reserved for future per-tier limits)
429 Rate-limit hit
500 Server fault

The webhook endpoint deliberately returns 404 webhook-not-found (not 401) on unknown tokens — this is the existing behavior and a few existing integrations depend on it. Don't probe-test tokens against the webhook endpoint expecting 401.

Best practices

  • One system + token per source class. Azure events go to one system, ADO to another, GitHub to a third. Independent rotation, cleaner filtering.
  • Pick a retention that matches the source. ADO/GitHub: 1825 days (5y) — these are activity-history. Event Grid: same. Short retention here doesn't save much (metadata is cheap) and loses audit value.
  • Test with Test connection (most webhook UIs have this) before you wire up production. A successful test puts an event in the portal Messages view within seconds.
  • Don't share the URL. Treat it like a bearer token — anyone with the URL can ingest as that source.

What's next

Goal Doc
Full Event Grid provisioning details ../Event-Grid-Ingest.md
ADO-specific event-type catalog and walkthrough 71-integrations-azure-devops.md
Webhook-vs-pull tradeoffs for M365 70-integrations-m365.md

Troubleshooting

Event Grid says "Subscription validation failed" when I create the subscription. The URL is wrong, the token is revoked, or the system is suspended. Test from curl:

curl -X POST "https://api.activitylog.com/api/v1/webhooks/alw_REPLACE_WITH_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[{"eventType":"Microsoft.EventGrid.SubscriptionValidationEvent","data":{"validationCode":"test123"}}]'

Should return {"validationResponse":"test123"}. If not, check the system's status in the portal.

ADO test says success but no message appears. Check the response code in ADO's Service Hook history (Project Settings → Service hooks → History tab). 201/202 = success; anything else, the event didn't store. Most common cause: ADO is configured with a stale webhook URL after a token rotation.

I want HMAC signature validation on incoming events. Set a signing secret when you generate (or rotate) the webhook URL; see Signature checking. It covers senders that use GitHub's X-Hub-Signature-256 header. Azure DevOps and Event Grid system topics do not sign requests that way.