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.detected today.

  • 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:

  1. Read the raw request body before parsing it. Parsing and re-serializing JSON changes the bytes, and the signature will no longer match.
  2. Split X-Arche-Signature on commas and read t and v1.
  3. Reject the delivery if t is more than five minutes from your current time. This stops someone replaying a captured delivery later.
  4. Compute the HMAC over "<t>." followed by the raw body, and compare it with v1 using 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_id is the version it revises. /v1/edgar/statements/{statement_version_id}/fact-revisions lists every fact that changed between them.

  • Name
    materiality_score
    Type
    string
    Description

    NONE, LOW, MEDIUM, HIGH, or CRITICAL. materiality_summary explains 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.

Was this page helpful?