Webhooks
Signed tenant webhooks
Attesto verstuurt signed HTTP POST deliveries voor belangrijke lifecycle events. Je endpoint moet de signature verifiëren, delivery IDs dedupliceren en binnen 10 seconden een 2xx response teruggeven.
Wanneer webhooks gebruiken
Webhooks melden aan je systemen dat Attesto evidence is veranderd. Gebruik ze voor workflow automation: start een interne review wanneer een bundle klaar is, meld een case system wanneer een checkpoint sluit, of pauzeer reliance op een stream wanneer fork evidence verschijnt. Behandel een webhook niet als de evidence zelf. Haal of verifieer de gerefereerde receipt, checkpoint, fork evidence of bundle voordat je erop vertrouwt.
| Need | Use | Why |
|---|---|---|
| Receive lifecycle notifications | Tenant webhooks | Attesto roept je endpoint aan nadat evidence verandert. |
| Send source evidence into Attesto | Connectors or SDK/API | Je systeem maakt het event; Attesto receiptt het. |
| Verify a received bundle | Offline verifier or POST /v2/verify | Verification controleert evidence integrity; notification doet dat niet. |
Supported events
| Event | Wanneer het fires |
|---|---|
batch.confirmed | Een Merkle batch van tenant events is on-chain bevestigd. |
batch.failed | Een batch is permanent gefaald of heeft de orphan window overschreden. |
event.anchored | Per-event notification nadat de related batch is bevestigd. |
proofstream.checkpointed | Een Proofstream checkpoint is gesloten en beschikbaar voor verificatie. |
proofstream.fork_detected | Fork evidence is aangemaakt voor een stream history conflict. |
proofstream.bundle_ready | Een verifier bundle is klaar voor de geconfigureerde range. |
Maak een subscription
: "${ATTESTO_WEBHOOK_URL:?set this to an HTTPS endpoint controlled by your organization}"
curl -X POST https://dashboard.attesto.eu/v1/webhooks \
-H "Authorization: Bearer $ATTESTO_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @- <<JSON
{
"name": "audit-notifications",
"url": "$ATTESTO_WEBHOOK_URL",
"eventTypes": ["batch.confirmed", "batch.failed"],
"description": "Posts lifecycle notifications to the configured endpoint"
}
JSON
Verifieer delivery signatures
Elke delivery bevat deze headers:
X-Attesto-Event: event type.X-Attesto-Delivery-Id: unieke delivery attempt ID.X-Attesto-Timestamp: Unix seconds gebruikt in de signature.X-Attesto-Signature: HMAC-SHA256 overtimestamp + "." + raw_body.
Delivery body shape:
{
"id": "delivery_...",
"event": "proofstream.bundle_ready",
"tenant_id": "tenant_...",
"created_at": "2026-06-07T12:00:00Z",
"data": {
"stream_id": "str_...",
"bundle_id": "bundle_...",
"from_seq_no": 1,
"to_seq_no": 250,
"verification_url": "https://verify.attesto.eu/v2/verify"
}
}
import hashlib
import hmac
import time
def verify(signing_value: str, body: bytes, timestamp: str, signature: str) -> bool:
ts = int(timestamp)
if abs(int(time.time()) - ts) > 300:
return False
expected = hmac.new(
signing_value.encode("ascii"),
f"{ts}.".encode("ascii") + body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)
Minimale Node handler
import crypto from "node:crypto";
import express from "express";
const app = express();
app.use(express.raw({ type: "application/json" }));
function verifyDelivery(signingValue, body, timestamp, signature) {
const ts = Number(timestamp);
if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > 300) {
return false;
}
const expected = crypto
.createHmac("sha256", signingValue)
.update(`${timestamp}.`)
.update(body)
.digest("hex");
const expectedBytes = Buffer.from(expected, "hex");
const receivedBytes = Buffer.from(signature || "", "hex");
return expectedBytes.length === receivedBytes.length
&& crypto.timingSafeEqual(expectedBytes, receivedBytes);
}
app.post("/attesto/webhook", async (req, res) => {
const ok = verifyDelivery(
process.env.ATTESTO_WEBHOOK_SIGNING_VALUE,
req.body,
req.header("X-Attesto-Timestamp"),
req.header("X-Attesto-Signature"),
);
if (!ok) return res.status(401).end();
const deliveryId = req.header("X-Attesto-Delivery-Id");
const payload = JSON.parse(req.body.toString("utf8"));
await recordDeliveryOnce(deliveryId, payload);
res.status(204).end();
});
Retry en dedupe
Delivery is at least once. Elke non-2xx response, timeout of refused
connection wordt opnieuw geprobeerd. Dedupe op
X-Attesto-Delivery-Id vóór side effects.
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 30 seconds |
| 3 | 2 minutes |
| 4 | 10 minutes |
| 5 | 1 hour |
| 6 | 6 hours |
| 7 | Final attempt |
Endpoint requirements
Gebruik HTTPS, verifieer de raw body vóór parsing, bewaar delivery IDs voor dedupe, return snel en verplaats duur processing naar je eigen queue nadat signature verification is geslaagd.
