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.
| Type | Trigger 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
- Idempotency. Dispatch is at-least-once. Consumers are expected to deduplicate on the envelope
idand treat repeated arrivals of the same identifier as the same event. - Timeout. The office expects a response within five seconds. Endpoints that exceed this limit are treated as failed and retried under the standard backoff schedule.
- Acknowledge promptly. Return a 2xx status as soon as the request is validated and accepted into the consumer's own durable queue. Processing should be deferred to an asynchronous worker so that downstream latency does not block acknowledgement.
- Secret custody. The signing secret is to be held server-side, in environment configuration or a dedicated secret store. It is not to be embedded in source repositories, client-side code, mobile binaries, or build artifacts.
- Replay window. Reject envelopes whose
tvalue lies outside a tolerated window from the receiving clock. A few minutes is conventional; the appropriate width is a matter of the consumer's threat model.
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.