Webhook Payload
This is what arrives at the url on your subscription.
Request
Agility sends an HTTP POST with a JSON body.
Headers Sent to Your Endpoint
| Header | Value | Sent when |
|---|---|---|
Content-Type | application/json | Always. |
Authorization | Verbatim value of the subscription's authorizationHeader | Only if the subscription has a non-empty one. |
X-SIGNATURE-HMAC-SHA512 | Base64 HMAC-SHA512 of the raw body, keyed with your secret | Only if the subscription has a non-empty secret. |
X-SIGNATURE | Legacy signature, computed differently | Only where the instance has legacy signatures enabled. |
No fixed User-Agent is set, so do not filter on one.
Body
{
"events": [
{
"webhookId": "YYY",
"sequenceId": 1,
"eventType": "AssetChanged",
"timestamp": "UTC timestamp",
"instigator": {
"_oid": "Member:20",
"href": "https://V1Host/V1Instance/assetdetail.v1?oid=Member:20",
"name": "Administrator",
"nickName": "Admin",
"email": "admin@admin.com",
"role": "Role:2",
"avatar": "https://V1Host/V1Instance/Image.mvc/Show?imageOid=Image:192923"
},
"targetAsset": {
"_oid": "Story:123:456",
"assetType": "Story",
"href": "https://V1Host/V1Instance/assetdetail.v1?oid=Story:123"
},
"changes": [
{
"name": "Name",
"old": "Original Name",
"new": "New Name",
"act": "Set"
},
{
"name": "Owners",
"new": "Member:20",
"act": "Added"
}
],
"snapshot": [
{
"_oid": "Story:123",
"Owners.Name": ["Administrator"],
"Owners.ID": [{ "_oid": "Member:20" }],
"SubsAndMe.Owners.Name": ["Administrator"]
}
]
}
]
}
The href values are asset permalinks —
assetdetail.v1 URLs that open the asset in the product UI, built for the
humans on the receiving end of a delivery rather than for programs.
events is an arrayA single delivery can carry more than one event. Iterate; do not read events[0] and stop.
Event Fields
| Field | Type | Description |
|---|---|---|
webhookId | string | The identifier you chose when creating the subscription. Use it to route the delivery. |
sequenceId | integer | Monotonic sequence number. Use it to restore ordering. |
eventType | string | AssetCreated or AssetChanged — which event type rule fired. |
timestamp | string | UTC timestamp of the change. |
instigator | object | The member who made the change. |
targetAsset | object | The asset that was created or changed. |
changes | array | What actually changed. Empty or absent for AssetCreated. |
snapshot | array | Projection of the asset, shaped by the event type's select. |
instigator
| Field | Description |
|---|---|
_oid | Oid token of the member, e.g. Member:20. |
href | Deep link to the member's detail page in the Agility UI. |
name | Full name. |
nickName | Short name. |
email | Email address. |
role | Oid token of the member's role, e.g. Role:2. |
avatar | URL to the avatar image. |
targetAsset
| Field | Description |
|---|---|
_oid | Oid token of the asset — often including a moment, e.g. Story:123:456. |
assetType | The asset type system name. |
href | Deep link to the asset in the Agility UI. |
targetAsset._oid may carry a momentStory:123:456 is Story 123 at version 456, not Story 123. If you use this value as a
correlation key or pass it back into the API expecting current data, strip the third segment
first. Note that href and snapshot[]._oid in the same payload use the momentless form
(Story:123) — the two are not interchangeable.
changes[]
One entry per attribute affected.
| Field | Description |
|---|---|
name | The attribute definition name. |
old | Previous value. Absent where there was none — including on relation adds. |
new | New value. |
act | What was done: Set, Added, Removed. |
Scalar attributes are Set and carry both old and new. Multi-value relations are Added or
Removed and carry only new — the Oid token of the value added or removed, not the whole
resulting collection.
snapshot[]
The projection requested by the event type's select,
in the same shape a query.v1 result would take: _oid plus one key per
selected path. Multi-value projections are arrays, and relation projections without a further
attribute come back as { "_oid": ... } objects.
With no select on the event type, snapshot carries little more than the _oid.
Verifying the Payload
If you set a secret on the subscription, every delivery carries
X-SIGNATURE-HMAC-SHA512: the Base64-encoded HMAC-SHA512 of the raw UTF-8 request body,
keyed with your secret.
Compute the HMAC over the exact bytes received, before any JSON parsing or re-serialization. Parsing and re-encoding will change whitespace and key order and the signature will not match. Capture the raw body first, verify, then parse.
import base64
import hashlib
import hmac
def is_signature_valid(raw_body: bytes, secret: str, header_value: str) -> bool:
expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha512).digest()
return hmac.compare_digest(
base64.b64encode(expected).decode("ascii"),
header_value,
)
Always compare in constant time, as above.
secret was undocumentedNeither the secret subscription field nor the signature headers appeared in the published
reference material.
secret is write-only from the API's point of view: it is accepted on
create and update, but it is never returned by
query or included in delivery receipts. Store
your copy of it when you set it.
authorizationHeader Versus secret
They solve different problems and are worth using together:
| Field | Direction | Purpose |
|---|---|---|
authorizationHeader | Agility proves itself to you | Sent verbatim as the Authorization header so your endpoint can authenticate the caller. |
secret | Agility proves the payload | Signs the body, so you know it was not altered and really came from your instance. |
A static Authorization value proves the caller knows a shared token, but does nothing about
payload integrity, and it is replayable if intercepted. The HMAC signature covers the body.
Responding
Return 200 OK. Any other status counts as a failed delivery, and consecutive failures
take the subscription out of service — see
delivery semantics.
Acknowledge quickly and process asynchronously: enqueue the payload, return 200, and do the
work outside the request. A slow handler produces timeouts, which count as failures.
Make handlers idempotent. Deduplicate on webhookId plus sequenceId; a retry after a
response you actually processed will redeliver the event.
Related Topics
- Event types — controlling what gets delivered
- Delivery status — what your endpoint sent back
- Create a subscription