Webhooks
Signed Tenant Webhooks
Attesto sendet signed HTTP POST Deliveries für wichtige Lifecycle Events. Ihr Endpoint muss die Signature verifizieren, Delivery IDs deduplizieren und innerhalb von 10 Sekunden eine 2xx Response zurückgeben.
Wann Webhooks nutzen
Webhooks informieren Ihre Systeme, dass Attesto Evidence geändert wurde. Nutzen Sie sie für Workflow Automation: interne Review starten, wenn ein Bundle bereit ist, ein Case System benachrichtigen, wenn ein Checkpoint schließt, oder Reliance auf einen Stream pausieren, wenn Fork Evidence erscheint. Behandeln Sie einen Webhook nicht als die Evidence selbst. Fetch oder verify das referenzierte Receipt, Checkpoint, Fork Evidence oder Bundle, bevor Sie sich darauf verlassen.
| Need | Use | Why |
|---|---|---|
| Receive lifecycle notifications | Tenant webhooks | Attesto ruft Ihren Endpoint nach Evidence Changes auf. |
| Send source evidence into Attesto | Connectors or SDK/API | Ihr System erstellt das Event; Attesto receiptiert es. |
| Verify a received bundle | Offline verifier or POST /v2/verify | Verification prüft Evidence Integrity; Notification nicht. |
Supported Events
| Event | Wann es auslöst |
|---|---|
batch.confirmed | Ein Merkle Batch von Tenant Events wurde on-chain bestätigt. |
batch.failed | Ein Batch ist permanent fehlgeschlagen oder überschritt das Orphan Window. |
event.anchored | Per-event Notification, nachdem der related Batch bestätigt wurde. |
proofstream.checkpointed | Ein Proofstream Checkpoint wurde geschlossen und ist für Verification verfügbar. |
proofstream.fork_detected | Fork Evidence wurde für einen Stream History Conflict erstellt. |
proofstream.bundle_ready | Ein Verifier Bundle ist für die konfigurierte Range bereit. |
Subscription erstellen
: "${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
Delivery Signatures verifizieren
Jede Delivery enthält diese Headers:
X-Attesto-Event: Event Type.X-Attesto-Delivery-Id: eindeutige Delivery Attempt ID.X-Attesto-Timestamp: Unix Seconds für die Signature.X-Attesto-Signature: HMAC-SHA256 übertimestamp + "." + 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)
Minimaler 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 und Dedupe
Delivery ist at least once. Jede non-2xx Response, Timeout oder
refused Connection wird retryed. Dedupe nach
X-Attesto-Delivery-Id vor 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
Verwenden Sie HTTPS, verifizieren Sie den Raw Body vor dem Parsing, speichern Sie Delivery IDs für Dedupe, antworten Sie schnell und verschieben Sie teures Processing in Ihre eigene Queue, nachdem Signature Verification erfolgreich war.
