Skip to main content

Webhooks

Webhooks let Walker Street Data notify your application when an event occurs - for example, when a job finishes enriching - so you don't have to poll for status. We POST a JSON payload to a URL you register, signed so you can verify it came from us.

Events

Subscribe to one event type per subscription:

Event typeFires when
job.completedA job finishes successfully (status Completed)
job.failedA job fails outright (status Failed)
job.extraction.completedThe PDF extraction stage of a job finishes
job.extraction.failedThe PDF extraction stage of a job fails
job.enrichment.completedThe enrichment stage of a job finishes
job.enrichment.failedThe enrichment stage of a job fails
monitoring.rule.triggeredAn in-life monitoring rule matches for a customer

Create a subscription

Register a subscription with the event type you want to receive and the URL we should call. The URL must be an absolute HTTPS URL - plain http:// is rejected with 400. A successful create returns 201 Created.

curl -X POST "https://api.walkerstdata.com.au/v1/webhooks/subscriptions" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"eventType": "job.completed",
"webhookUrl": "https://your-app.example.com/webhooks/wsd",
"customerIds": []
}'

The response includes the subscription ID and a signing secret. The secret is a Base64-encoded string and is only returned on create and on rotation - store it securely now, as it can't be retrieved later.

{
"data": {
"webhookSubscriptionId": "b3c1e2d4-5f6a-7890-abcd-ef1234567890",
"eventType": "job.completed",
"webhookUrl": "https://your-app.example.com/webhooks/wsd",
"secret": "k9Xa2p7Qc1d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0=",
"customerIds": [],
"isCatchAll": true,
"dateCreated": "2026-07-02T00:00:00Z"
},
"message": null
}

The webhook payload

We POST a JSON body with Content-Type: application/json. Every payload carries a common envelope:

FieldTypeDescription
eventTypestringThe event type (matches your subscription)
eventIduuidUnique ID for this event - use it to deduplicate
clientIduuidYour client ID
customerIduuidThe customer the event relates to
timestampstringISO 8601 time the event was generated

Payloads can also carry internal fields (such as traceparent, tracestate, and tenantId, often null). Ignore fields you don't recognise - new ones may be added without notice - and don't fail parsing on unknown or null fields.

Job events (job.*) add jobId, abn, and jobType, plus event-specific fields:

Event typeAdditional fields
job.completedtotalProcessedTransactions, totalReceivedTransactions
job.extraction.completedextractedCount, duplicateCount
job.enrichment.completedenrichedCount, duplicateCount
job.failed, job.extraction.failed, job.enrichment.failederror (nullable)
monitoring.rule.triggeredevaluationResultId, monitoringRuleId, ruleName, runDate, interpretation[], recommendedAction[] (no jobId/abn/jobType)

Example job.completed delivery:

{
"eventType": "job.completed",
"eventId": "e7c9a1b2-3d4e-5f60-7182-93a4b5c6d7e8",
"clientId": "a0ced7e1-da3a-40ea-a18f-4c829f816c6a",
"customerId": "524ba9bc-06e7-417e-bd54-b99785f5194a",
"timestamp": "2026-07-08T02:15:30Z",
"jobId": "34e1bbd4-36be-4d95-9351-a047eed5bf97",
"abn": "12345678901",
"jobType": "Enrichment",
"totalProcessedTransactions": 118,
"totalReceivedTransactions": 120
}

Verifying deliveries

Every delivery includes an X-Cypher-Webhook-Signature header. Verify it before acting on the payload, and reject anything that doesn't match.

The signature is sha256= followed by the lowercase hex HMAC-SHA256 of the raw request body, keyed by your subscription secret decoded from Base64:

X-Cypher-Webhook-Signature: sha256=3b2a1f...<hex>

Compute the expected value over the exact bytes you received (do not re-serialize the JSON - whitespace differences will break the match) and compare in constant time. Node.js example:

const crypto = require('crypto');

function verifyWebhook(rawBody, signatureHeader, secretBase64) {
const key = Buffer.from(secretBase64, 'base64'); // secret is Base64 - decode to raw key bytes
const expected =
'sha256=' + crypto.createHmac('sha256', key).update(rawBody).digest('hex');

const received = Buffer.from(signatureHeader ?? '');
const computed = Buffer.from(expected);
return (
received.length === computed.length &&
crypto.timingSafeEqual(received, computed)
);
}

If a secret is ever exposed, rotate it. The old secret is invalidated immediately:

curl -X POST "https://api.walkerstdata.com.au/v1/webhooks/subscriptions/{subscriptionId}/secret/rotate" \
-H "x-api-key: YOUR_API_KEY"

Delivery behaviour

  • Success is any 2xx response from your endpoint. Return quickly (within 30 seconds) - do heavy processing asynchronously after acknowledging.
  • Failed deliveries (non-2xx, timeout, or unreachable) are recorded but are not currently retried automatically. Treat webhooks as a fast-path notification and reconcile with a periodic job status poll as a safety net.
  • Idempotency: deliveries can arrive more than once. Deduplicate on eventId, and make your handler idempotent.

Scoping to customers

Provide customerIds to scope a subscription to specific customers. Leave the list empty to create a catch-all subscription that receives events for every customer. You can add customers to an existing subscription later:

curl -X POST "https://api.walkerstdata.com.au/v1/webhooks/subscriptions/{subscriptionId}/customers" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "customerIds": ["524ba9bc-06e7-417e-bd54-b99785f5194a"] }'

To remove customers from a subscription, send DELETE to the same path with the customerIds to remove.

Removing the last customer makes the subscription catch-all

A subscription with an empty customer list is a catch-all. If you remove all customers from a scoped subscription, it reverts to receiving events for every customer - it does not go quiet.

Managing subscriptions

List your subscriptions (paginated - see Pagination):

curl -X GET "https://api.walkerstdata.com.au/v1/webhooks/subscriptions" \
-H "x-api-key: YOUR_API_KEY"

Two things to be aware of:

  • Duplicate subscriptions are allowed. Registering the same event type and URL twice creates two independent subscriptions, and your endpoint will receive each event twice (with different signatures). If you're retrying a subscription create that may have succeeded, list your subscriptions first rather than blindly re-creating.
  • Subscriptions can't currently be deleted via the API. If you need a subscription removed, contact us. Until it's removed, the subscription keeps delivering - have your endpoint acknowledge (2xx) and discard events you no longer want.