Documentation

Webhooks.

The office dispatches signed HTTP notifications to a destination of the account-holder's choosing when relevant events occur on the account. The mechanism is intended for integration with downstream systems of record; it does not replace the receipt, which remains the canonical instrument.

Overview

A webhook is an HTTP POST request issued by Orphograph to a URL nominated by the account-holder. Each request carries a JSON envelope in the body and a signature header derived from a shared secret. The transport is TLS; the body is signed with HMAC-SHA256. Dispatch is reserved for accounts holding an active Standing Order subscription.

The protocol is modelled after the convention established for transactional dispatch in adjacent industries. The verification step is identical in spirit and is reproduced in full below.

Events currently dispatched

The following event types are issued. Additional types may be introduced; consumers are expected to ignore types they do not recognise.

TypeTrigger and payload
anchor.created Issued when the account-holder anchors a file and the calendar attestations are received. The data object contains receipt_id, hash_hex, sha512_hex, created_at, client_label, calendars_ok, calendars_total, private (boolean), and receipt_url.
anchor.btc_pinned Issued when a previously-issued receipt has been committed to a Bitcoin block. The data object contains receipt_id, hash_hex, btc_pinned_at, pinned_count, pinned_total, status, and receipt_url.
subscription.renewed Issued at the rollover of a Stripe billing period for an active Standing Order. The payload schema is being finalised and will be published in a subsequent revision of this document.

Envelope

Every dispatch carries the same outer envelope. The id is unique to the event and is the value on which consumers should deduplicate. The created field is a UNIX timestamp in seconds.

{
  "id": "evt_xxxxxxxxxxxx",
  "type": "anchor.created",
  "created": 1747600000,
  "data": { ... event-specific payload ... }
}

Signature

Each request carries an X-Orpho-Signature header of the form t=1747600000,v1=<64-char hex hmac-sha256>. The signed payload is the literal concatenation of bytes <timestamp>.<request body>, where <timestamp> is the value of t and the dot is a single ASCII period. The signing key is the secret issued to the account-holder at the time of webhook registration.

Consumers are expected to recompute the signature on receipt and reject any request whose signature does not match in constant time, and to reject requests whose timestamp lies outside a tolerated window from the receiving clock.

The following stdlib reference is sufficient for verification in Python:

import hmac, hashlib
def verify(secret: str, body: bytes, signature_header: str) -> bool:
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

The body passed to verify must be the exact bytes received on the wire. Any reserialisation of the JSON, normalisation of whitespace, or re-encoding of the body will cause the signature to fail.

Recommended consumer practice

Registration

Destination URLs are nominated and revoked through the management interface at /account.html. Each registered destination is issued its own signing secret, which is displayed once at the moment of creation and is not retrievable thereafter. A new secret may be issued at any time; the prior secret is invalidated on issuance.

Only account-holders with an active Standing Order subscription may register destinations. Suspension of the subscription suspends dispatch; reinstatement resumes it without further configuration.

Questions on the dispatch protocol may be put to the office at [email protected]. Revisions to this document are themselves anchored.