Webhooks
When a company revises figures it has already filed, Arche records the change
as a restatement alert. Webhooks push each alert to an endpoint you control, so
you learn about a restatement when Arche detects it rather than on your next
poll of /v1/edgar/restatement-alerts.
Webhooks are available from the Developer plan. Developer allows up to three endpoints; Growth and above have no limit.
Every delivery is signed with a secret that belongs to your subscription alone. Verify the signature before acting on a delivery. An unverified request could have come from anyone.
What Arche sends
Arche sends one event type, restatement.detected, as an HTTPS POST with a
JSON body. Each request carries these headers:
- Name
X-Arche-Signature- Type
- string
- Description
t=<unix seconds>,v1=<hex>. Verify it as described below.
- Name
X-Arche-Event-Type- Type
- string
- Description
Always
restatement.detectedtoday.
- Name
X-Arche-Delivery-Id- Type
- uuid
- Description
Identifies this delivery. It stays the same across retries of the same delivery.
- Name
X-Arche-Dedupe-Key- Type
- string
- Description
Stable key for this alert and subscription. Use it to ignore a delivery you have already processed.
Registering an endpoint
Register endpoints on the Webhooks page of the
developer portal. Endpoints belong to an
environment, so register one under each environment you want alerts for. The
URL must use https://.
When you add an endpoint, the portal shows its signing secret once. It starts
with whsec_. Store it where your receiver can read it, such as a secrets
manager. The portal does not show it again, but you can rotate it at any time.
Adding a URL that is already registered returns the existing endpoint and its current secret rather than creating a second one.
Verifying signatures
The signature is an HMAC-SHA256, keyed with your signing secret, over the timestamp, a period, and the raw request body:
signed_content = "<t>." + <raw request body bytes>
v1 = hex(HMAC_SHA256(signing_secret, signed_content))
To verify a delivery:
- Read the raw request body before parsing it. Parsing and re-serializing JSON changes the bytes, and the signature will no longer match.
- Split
X-Arche-Signatureon commas and readtandv1. - Reject the delivery if
tis more than five minutes from your current time. This stops someone replaying a captured delivery later. - Compute the HMAC over
"<t>."followed by the raw body, and compare it withv1using a constant-time comparison.
Verify a delivery
import hashlib
import hmac
import time
def verify_arche_signature(
secret: str, header: str, raw_body: bytes, tolerance_seconds: int = 300
) -> bool:
parts = dict(item.strip().split("=", 1) for item in header.split(",") if "=" in item)
timestamp, received = parts.get("t"), parts.get("v1")
if not timestamp or not received or not timestamp.isdigit():
return False
if abs(time.time() - int(timestamp)) > tolerance_seconds:
return False
expected = hmac.new(
secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, received)
Respond with any 2xx status once you have verified and stored the delivery.
Do the slower work, such as rerunning a model, after you respond.
Frameworks that parse JSON automatically often discard the raw body. In
Express, mount the express.raw middleware with type
application/json on the webhook route. In Flask, read
request.get_data().
Delivery and retries
Arche checks for undelivered alerts every ten minutes, so a delivery usually arrives within ten minutes of detection.
A delivery succeeds when your endpoint returns a 2xx status within ten
seconds. Any other status, a timeout, or a connection failure counts as a
failure. Arche makes up to three attempts in total. Retries happen on later
runs, waiting at least 30 seconds after the first failure and at least five
minutes after the second. After the third failure Arche stops trying.
A missed delivery never loses the alert. Every alert remains available from
/v1/edgar/restatement-alerts, so you can reconcile after an
outage.
Deliveries can arrive more than once, and not always in the order the alerts
were detected. Use X-Arche-Dedupe-Key to skip a delivery you have already
processed, and detected_at in the body to order alerts.
Rotating your secret
Rotate the secret if it may have been exposed. Rotation takes effect
immediately: every delivery sent afterwards is signed with the new secret, and
there is no period where both are valid. Deploy the new secret to your receiver
as soon as you receive it, and expect deliveries to fail verification until it
is in place. Deliveries that fail verification should return a non-2xx
status, so Arche retries them.
To rotate a secret, choose Rotate secret next to the endpoint on the Webhooks page. The new secret is shown once.
Removing an endpoint
Choose Remove next to the endpoint on the
Webhooks page. Arche stops sending to it
immediately, including any retries still pending. Alerts remain available from
/v1/edgar/restatement-alerts. Adding the same URL again later creates a new
endpoint with a new signing secret.
Payload reference
A delivery for a balance-sheet amendment looks like this. Values are illustrative.
{
"event_type": "restatement.detected",
"restatement_alert_event_id": "5f0c9a1e-2b7d-4c1a-9e3f-8d2a6b4c7e10",
"dedupe_key": "restatement.detected:0001069183:BALANCE_SHEET:2024:FY:0001069183-25-000019:0001069183-25-000075",
"delivery_dedupe_key": "restatement.detected:0001069183:BALANCE_SHEET:2024:FY:0001069183-25-000019:0001069183-25-000075:9b1e4d2c-6a3f-4f8e-b0c1-2d7e5a9f4b36",
"detected_at": "2025-05-07T21:14:02Z",
"company_id": "a3d1c6e2-7f4b-4b9a-8c2e-1f6d5b3a9e70",
"cik": "0001069183",
"ticker": "AXON",
"accession_id": "0001069183-25-000075",
"statement_version_id": "c7e2a4b9-1d3f-4e6a-9b8c-5f0d2e7a1c34",
"prior_statement_version_id": "e1b5d8f3-6c2a-4f9e-8d7b-3a0c4e6f2b91",
"statement_type": "BALANCE_SHEET",
"statement_date": "2024-12-31",
"fiscal_year": 2024,
"fiscal_period": "FY",
"facts_changed_count": 2,
"material_facts_count": 1,
"materiality_score": "HIGH",
"change_class_counts": { "VALUE_REVISION": 2 },
"summary_metrics": [
{
"metric_code": "total_current_liabilities",
"change_type": "VALUE_REVISION",
"old_value": "997586000",
"new_value": "1677875000",
"delta_value": "680289000"
}
],
"materiality_summary": "HIGH severity across 2 fact revisions",
"model_impacts": []
}
- Name
restatement_alert_event_id- Type
- uuid
- Description
The alert. Fetch it from
/v1/edgar/restatement-alerts/{id}for the full record.
- Name
statement_version_id- Type
- uuid
- Description
The new statement version.
prior_statement_version_idis the version it revises./v1/edgar/statements/{statement_version_id}/fact-revisionslists every fact that changed between them.
- Name
materiality_score- Type
- string
- Description
NONE,LOW,MEDIUM,HIGH, orCRITICAL.materiality_summaryexplains the rating.
- Name
summary_metrics- Type
- array
- Description
The most significant metric changes, as decimal strings.
- Name
model_impacts- Type
- array
- Description
Effects on derived model inputs, such as margins, when Arche can compute them.