Webhooks
Signed tenant webhooks
Attesto invia signed HTTP POST deliveries per lifecycle events importanti. Il tuo endpoint deve verificare la signature, deduplicare delivery IDs e restituire una response 2xx entro 10 secondi.
Quando usare webhooks
Webhooks notifica ai tuoi sistemi che Attesto evidence è cambiata. Usali per workflow automation: avvia una review interna dopo che un bundle è pronto, notifica un case system dopo la chiusura di un checkpoint, o sospendi reliance su uno stream quando appare fork evidence. Non trattare un webhook come evidence stessa. Recupera o verifica il receipt, checkpoint, fork evidence o bundle referenziato prima di farvi affidamento.
| Need | Use | Why |
|---|---|---|
| Receive lifecycle notifications | Tenant webhooks | Attesto chiama il tuo endpoint dopo cambiamenti di evidence. |
| Send source evidence into Attesto | Connectors or SDK/API | Il tuo sistema crea l'event; Attesto lo receiptta. |
| Verify a received bundle | Offline verifier or POST /v2/verify | Verification controlla evidence integrity; notification no. |
Supported events
| Event | Quando scatta |
|---|---|
batch.confirmed | Un Merkle batch di tenant events è stato confermato on-chain. |
batch.failed | Un batch è fallito permanentemente o ha superato l'orphan window. |
event.anchored | Per-event notification dopo la conferma del related batch. |
proofstream.checkpointed | Un Proofstream checkpoint si è chiuso ed è disponibile per verification. |
proofstream.fork_detected | Fork evidence creata per uno stream history conflict. |
proofstream.bundle_ready | Un verifier bundle è pronto per la range configurata. |
Crea una 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
Verifica delivery signatures
Ogni delivery include questi headers:
X-Attesto-Event: event type.X-Attesto-Delivery-Id: ID univoco di delivery attempt.X-Attesto-Timestamp: Unix seconds usati nella signature.X-Attesto-Signature: HMAC-SHA256 sutimestamp + "." + 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)
Minimal 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 e dedupe
Delivery è at least once. Ogni non-2xx response, timeout o refused
connection viene retryed. Dedupe per
X-Attesto-Delivery-Id prima dei 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
Usa HTTPS, verifica il raw body prima del parsing, archivia delivery IDs per dedupe, rispondi rapidamente e sposta il processing costoso nella tua queue dopo che signature verification riesce.
