Webhooks
Signed tenant webhooks
Attesto wysyła signed HTTP POST deliveries dla ważnych lifecycle events. Twój endpoint musi zweryfikować signature, deduplikować delivery IDs i zwrócić response 2xx w ciągu 10 sekund.
Kiedy używać webhooks
Webhooks informują twoje systemy, że Attesto evidence się zmieniła. Używaj ich do workflow automation: rozpocznij internal review po przygotowaniu bundle, powiadom case system po zamknięciu checkpoint albo wstrzymaj reliance na stream, gdy pojawi się fork evidence. Nie traktuj webhook jako samej evidence. Pobierz albo zweryfikuj wskazany receipt, checkpoint, fork evidence lub bundle, zanim na nim polegasz.
| Need | Use | Why |
|---|---|---|
| Receive lifecycle notifications | Tenant webhooks | Attesto wywołuje twój endpoint po zmianach evidence. |
| Send source evidence into Attesto | Connectors or SDK/API | Twój system tworzy event; Attesto wystawia receipt. |
| Verify a received bundle | Offline verifier or POST /v2/verify | Verification sprawdza evidence integrity; notification nie. |
Supported events
| Event | Kiedy fires |
|---|---|
batch.confirmed | Merkle batch tenant events został potwierdzony on-chain. |
batch.failed | Batch trwale się nie powiódł albo przekroczył orphan window. |
event.anchored | Per-event notification po potwierdzeniu related batch. |
proofstream.checkpointed | Proofstream checkpoint został zamknięty i jest dostępny do verification. |
proofstream.fork_detected | Fork evidence utworzona dla stream history conflict. |
proofstream.bundle_ready | Verifier bundle jest gotowy dla skonfigurowanej range. |
Utwórz 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
Weryfikuj delivery signatures
Każda delivery zawiera te headers:
X-Attesto-Event: event type.X-Attesto-Delivery-Id: unikalny delivery attempt ID.X-Attesto-Timestamp: Unix seconds użyte w signature.X-Attesto-Signature: HMAC-SHA256 nadtimestamp + "." + 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)
Minimalny 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 i dedupe
Delivery jest at least once. Każda non-2xx response, timeout albo
refused connection jest retry. Dedupe według
X-Attesto-Delivery-Id przed 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
Używaj HTTPS, weryfikuj raw body przed parsing, przechowuj delivery IDs dla dedupe, odpowiadaj szybko i przenieś kosztowne processing do własnej queue po pomyślnej signature verification.
