Attesto

Webhooks

Webhooks tenant signés

Attesto envoie des signed HTTP POST deliveries pour les lifecycle events importants. Votre endpoint doit vérifier la signature, déduper les delivery IDs et retourner une réponse 2xx en moins de 10 secondes.

Quand utiliser les webhooks

Les webhooks notifient vos systèmes lorsque l'evidence Attesto change. Utilisez-les pour workflow automation: démarrer une revue interne lorsqu'un bundle est prêt, notifier un case system après la fermeture d'un checkpoint, ou suspendre la reliance sur un stream quand une fork evidence apparaît. Ne traitez pas un webhook comme l'evidence elle-même. Récupérez ou vérifiez le receipt, checkpoint, fork evidence ou bundle référencé avant de vous y fier.

NeedUseWhy
Receive lifecycle notificationsTenant webhooksAttesto appelle votre endpoint après des changements d'evidence.
Send source evidence into AttestoConnectors or SDK/APIVotre système crée l'event; Attesto le receipt.
Verify a received bundleOffline verifier or POST /v2/verifyVerification contrôle l'evidence integrity; une notification ne le fait pas.

Supported events

EventQuand il se déclenche
batch.confirmedUn Merkle batch d'events tenant a été confirmé on-chain.
batch.failedUn batch a échoué définitivement ou dépassé l'orphan window.
event.anchoredNotification per-event après confirmation du batch lié.
proofstream.checkpointedUn Proofstream checkpoint s'est fermé et est disponible pour verification.
proofstream.fork_detectedFork evidence créée pour un conflit de stream history.
proofstream.bundle_readyUn verifier bundle est prêt pour la range configurée.

Créer une 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

Vérifier les delivery signatures

Chaque delivery inclut ces headers:

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)

Handler Node minimal

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 et dedupe

Delivery est at least once. Toute réponse non-2xx, timeout ou refused connection est retry. Dedupe par X-Attesto-Delivery-Id avant les side effects.

AttemptDelay
1Immediate
230 seconds
32 minutes
410 minutes
51 hour
66 hours
7Final attempt

Endpoint requirements

Utilisez HTTPS, vérifiez le raw body avant parsing, stockez les delivery IDs pour dedupe, répondez rapidement et déplacez le processing coûteux dans votre propre queue après succès de signature verification.