Status notifications
Register an endpoint and Simetrik calls you as each file moves — signed, authenticated, retried, and correlated with your own track id.
JaaS has no status endpoint to poll. Instead, you register a notification_config on the source and Simetrik posts to your endpoint every time a file changes state. It works identically for file ingestion and the webhook flow.
You register it in the same call that creates the source — either POST /api/v2/sources or POST /api/v2/webhooks.
Configuration
| Field | Type | Required | What it does |
|---|---|---|---|
target_url | string | Yes | Where to post. |
events | array | Yes | Which events you want. Anything not listed is never sent. |
auth_config | object | Yes | How Simetrik authenticates against your endpoint. |
signing_secret | string | No | Secret used to sign the payload. |
inbound_track_id_header_name | string | No | Header to read your track id from. Default X-Correlation-Id. |
outbound_track_id_field_key | string | No | Payload key the track id comes back under. Default correlation_id. |
{
"notification_config": {
"target_url": "https://hooks.example.com/simetrik/files",
"events": [
"download_status.completed",
"download_status.download_failed",
"download_status.validation_failed",
"download_status.processing_failed"
],
"auth_config": {"type": "bearer", "token": "…"},
"signing_secret": "…",
"inbound_track_id_header_name": "X-Trace-Id",
"outbound_track_id_field_key": "trace_id"
}
}Set your own signing_secret. If you leave it out one is generated for you, and it is never returned in the response — which leaves you with signed payloads you have no way to verify.
Events
| Event | Fires when |
|---|---|
download_status.processing | The file is moving through the pipeline. Fires more than once per file. |
download_status.completed | The file was delivered and reconciliation was notified. |
download_status.download_failed | The file couldn't be read or fetched. |
download_status.validation_failed | The content couldn't be parsed. File ingestion only. |
download_status.processing_failed | Delivery or the downstream notification failed. |
Which internal state maps to which event is listed per flow, in file ingestion and in the webhook flow.
The payload
{
"event_type": "download_status.completed",
"timestamp": "2026-09-14T18:04:21.512890+00:00",
"trace_id": "settlement-2026-09-14-run-3",
"data": {
"webhook_file_id": "b8f1c2e4-…",
"webhook_source_id": "61171_wh_9d200105",
"source_id": "61171",
"filename": "settlements-2026-09-14.csv",
"file_url": "https://files.example.com/settlements/2026-09-14.csv",
"status": "completed",
"file_size_bytes": 184320,
"downloaded_at": "2026-09-14T18:04:19.004112",
"file_resource_path": "…/61171/8c3e….csv"
}
}| Field | Always present | What it holds |
|---|---|---|
event_type | Yes | The event. |
timestamp | Yes | When the notification was built, UTC. |
trace_id | No | Your track id, under the key you configured — correlation_id unless you set outbound_track_id_field_key, as the source above did. Webhook flow only, and only when you sent one. |
data.webhook_file_id | Yes | The file's id in the webhook flow; the batch file name in file ingestion. |
data.webhook_source_id | Yes | The webhook id in the webhook flow; the source id in file ingestion. |
data.source_id | Yes | The source, as a string. |
data.filename | Yes | The file name. Generated by Simetrik in file ingestion, since you never named it. |
data.file_url | Yes | Where the file came from. Empty string when it doesn't apply. |
data.status | Yes | The last segment of event_type. |
data.file_size_bytes | No | Size, once known. |
data.downloaded_at | No | When the download finished. |
data.s3_key, data.file_resource_path | No | Where the file was delivered inside Simetrik. Useful when raising a support ticket. |
data.error.message | No | Present on failure events only. |
Headers
| Header | Value |
|---|---|
X-Webhook-Event | The event type. |
X-Webhook-Delivery-Id | A UUID for this delivery. Stable across retries — use it to discard duplicates. |
X-Webhook-Signature | sha256=<hex digest>. |
X-Trace-Id | Your track id, under the header derived from outbound_track_id_field_key — X-Correlation-Id by default, X-Trace-Id for the source above. Only when you sent one. |
Verifying the signature
The signature is an HMAC-SHA256 over the payload serialized with its keys sorted, using your signing_secret. Since the body arrives in its natural order, re-serialize it before computing the digest:
import hashlib
import hmac
import json
def is_authentic(raw_body: bytes, signature_header: str, signing_secret: str) -> bool:
payload = json.loads(raw_body)
canonical = json.dumps(payload, sort_keys=True, ensure_ascii=True).encode("utf-8")
expected = hmac.new(signing_secret.encode("utf-8"), canonical, hashlib.sha256).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature_header)Outside Python, three details have to match exactly, or the digest won't:
- Keys sorted at every level.
,between entries and:after each key — the separators Python writes by default.- Non-ASCII escaped as
\uXXXX. This is the one that bites: a filename with an accent is signed in its escaped form, so a serializer that emits raw UTF-8 produces a different digest.ensure_ascii=Trueis Python's default and is passed explicitly above to make the requirement visible.
The track id reaches these notifications from the webhook flow only — file ingestion batches records before processing, so there's no inbound call to carry one. It sits at the top level and is only present when you sent one. That's deliberate: clients that don't use it get a signature computed over exactly the same fields as before.
Authenticating against your endpoint
auth_config.type | Fields | What is sent |
|---|---|---|
none | — | Nothing. |
basic_auth | username, password | Authorization: Basic … |
api_key | api_key, api_key_header | Your key in the header you name. Defaults to X-API-Key. |
bearer | token | Authorization: Bearer … |
oauth2_client_credentials | token_url, client_id, client_secret, scope | A client-credentials token, fetched and cached until shortly before it expires, then sent as a bearer token. |
Delivery and retries
- 10 seconds per attempt, 3 attempts maximum.
- Retried on network errors and on
5xx. Backoff is exponential with jitter. - Not retried on
4xx— that's read as a permanent rejection. X-Webhook-Delivery-Idstays the same across retries, so deduplicate on it.
Notifications are fire-and-forget: a delivery that fails every attempt is logged on the Simetrik side and dropped. It never affects the file, which is already on its way. So treat these notifications as a fast path, not as the ledger — if one never arrives, the file may well have been delivered anyway.
Answer with any 2xx as soon as you've accepted the payload, and do your own work afterwards. Anything slower than 10 seconds reads as a failed attempt and gets retried.