Webhooks
Webhooks push protocol events to your endpoint as signed JSON. Manage subscriptions and send test payloads from the developer portal. Like the rest of ThesisLock there is no server of our own, so this documents the format and signing scheme you wire into your own delivery service, using the same events that drive the feeds and audit log.
Event types
anchor.created: a single document was anchored.batch.created: a batch of documents was anchored.group.anchor: a document was anchored to a group.proof.minted: a proof NFT was minted.group.created: a new group was created.group.member_added: a member was added to a group.
Payload
Each delivery is an HTTP POST whose body is a JSON object of { event, data, timestamp }:
{
"event": "anchor.created",
"data": {
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"label": "Thesis final draft",
"owner": "SP3QS6X01XKTYC84BHA0J567CZTAH67BJHN88FNVM",
"txId": "0x9f1e...eeff0",
"stacksBlock": 168420
},
"timestamp": "2026-06-21T15:00:00.000Z"
}Signature
The request carries an X-ThesisLock-Signature header of the form sha256=<hex>, an HMAC-SHA256 of the raw request body computed with your subscription's signing secret. The secret is shown once when you create the subscription. Verify it before trusting a payload:
import crypto from "node:crypto";
function verify(rawBody, signatureHeader, secret) {
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const received = Buffer.from(signatureHeader || "");
const expectedBuf = Buffer.from(expected);
// Equal lengths first: timingSafeEqual throws on a length mismatch.
return (
received.length === expectedBuf.length &&
crypto.timingSafeEqual(received, expectedBuf)
);
}import hmac, hashlib
def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature_header, expected)Retries
Delivery and retries are implemented by your integration. A common policy is to retry on any non-2xx response with exponential backoff (for example one minute, five minutes, thirty minutes) for a few attempts, then pause the subscription. The developer portal tracks a per-subscription fail count for visibility.
Testing
The portal's webhook tester sends a sample payload to your endpoint and shows the response. If your endpoint does not allow cross-origin requests from the browser, it falls back to a ready-to-run curl command.