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
- Sidebar → Systems → New System (or pick an existing one for webhook ingest).
- On the system detail page click New Token.
- In the dialog, check URL token (so it generates an
alw_…value instead ofal_…). - Pick a retention.
- 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.

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:
- Event Grid → System Topics → Create.
- Topic type: pick
Microsoft.Resources.Subscriptionsfor subscription-level CUD, or any other system topic you want to forward. - Source: the Azure subscription you want events from.
- Resource group + name: any.
- After creation, open the topic and + Event Subscription.
- Endpoint type: Webhook.
- Endpoint: paste
https://api.activitylog.com/api/v1/webhooks/alw_REPLACE_WITH_YOUR_TOKEN. - 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:
- Project Settings → Service hooks → + Create subscription.
- Service: Web Hooks.
- Trigger: pick an event type (
Code pushed,Pull request created,Work item updated,Build completed, etc.). - 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.
- URL:
- Test — should respond with HTTP 201/202.
- 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:
- Settings → Webhooks → Add webhook.
- Payload URL:
https://api.activitylog.com/api/v1/webhooks/alw_REPLACE_WITH_YOUR_TOKEN - Content type:
application/json. - Secret: leave blank (we don't validate HMAC today — coming).
- Which events: pick what you want.
Push events+Pull request+Workflow runsis a sensible starter set. - Active: ✓.
- 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 typeactor_id/actor_email— who did ittarget_id/target_url— what it acted onsummary— 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.