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).

Mint a webhook URL token

  1. Sidebar → SystemsNew System (or pick an existing one for webhook ingest).
  2. On the system detail page click New Token.
  3. In the dialog, check URL token (so it generates an alw_… value instead of al_…).
  4. Pick a retention.
  5. Generate, then copy the token.

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.

Token mint dialog with URL-token toggle

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 row tagged with the event type (git.push, git.pullrequest.created, workitem.updated, etc.).

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: leave blank (we don't validate HMAC today — coming).
  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. Our endpoint accepts the ping as a no-op success.

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
201 Single event stored — body is { "id": "mid_...", "ingestedAt": "..." }
202 Batch stored — body is { "messageIds": ["mid_...", ...] }
401 (rare — usually 404 below)
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. Not implemented today — both ADO and Event Grid system topics don't compute one by default. Tracked as a future hardening item; if you need it now, run an Azure Function relay that validates and re-forwards.