Webhooks
Signed tenant webhooks
Attesto envía signed HTTP POST deliveries para lifecycle events importantes. Su endpoint debe verificar la signature, deduplicar delivery IDs y devolver una respuesta 2xx en 10 segundos.
Cuándo usar webhooks
Webhooks notifican a sus sistemas que Attesto evidence cambió. Úselos para workflow automation: iniciar una revisión interna después de que un bundle esté listo, notificar un case system después de que cierre un checkpoint, o pausar reliance en un stream cuando aparece fork evidence. No trate un webhook como la evidence en sí. Obtenga o verifique el receipt, checkpoint, fork evidence o bundle referenciado antes de confiar en él.
| Need | Use | Why |
|---|---|---|
| Receive lifecycle notifications | Tenant webhooks | Attesto llama a su endpoint después de cambios de evidence. |
| Send source evidence into Attesto | Connectors or SDK/API | Su sistema crea el event; Attesto lo recibe. |
| Verify a received bundle | Offline verifier or POST /v2/verify | Verification revisa evidence integrity; notification no lo hace. |
Supported events
| Event | Cuándo se dispara |
|---|---|
batch.confirmed | Un Merkle batch de tenant events fue confirmado on-chain. |
batch.failed | Un batch falló permanentemente o superó la orphan window. |
event.anchored | Per-event notification después de confirmar el related batch. |
proofstream.checkpointed | Un Proofstream checkpoint cerró y está disponible para verification. |
proofstream.fork_detected | Fork evidence fue creada para un stream history conflict. |
proofstream.bundle_ready | Un verifier bundle está listo para la range configurada. |
Crear 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
Verificar delivery signatures
Cada delivery incluye estos headers:
X-Attesto-Event: event type.X-Attesto-Delivery-Id: ID único de delivery attempt.X-Attesto-Timestamp: Unix seconds usados en la signature.X-Attesto-Signature: HMAC-SHA256 sobretimestamp + "." + 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 y dedupe
Delivery es at least once. Cualquier respuesta non-2xx, timeout o
refused connection se reintenta. Dedupe por
X-Attesto-Delivery-Id antes de 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
Use HTTPS, verifique el raw body antes de parsear, almacene delivery IDs para dedupe, responda rápido y mueva procesamiento costoso a su propia queue después de que signature verification tenga éxito.
