ExistBefore

Bulk attestation guide

You already have the hashes. Skip the browser.

If your pipeline produces a daily cohort of pre-computed SHA-256 hashes — receipts, contracts, log archives, build artefacts, scientific data — pasting them into existbefore.com one at a time is the wrong tool. The browser flow is optimised for a single human attesting a single artefact. For batches, the right tools are the CertiSigma SDK (Python, JavaScript, Go, PHP) and the Census CLI — same T0 ECDSA, same T1 eIDAS qualified timestamp, same T2 Bitcoin anchor, no browser in the loop.

A developer key is enough to start. An enterprise contract is needed for SLA-backed volume, dedicated keys, and webhooks.

Why not the web flow?

CertiSigma SDK (Python).

Iterate over your batch, attest each hash, persist the returned attestation IDs. The SDK handles connection pooling, retries with exponential backoff, and structured error types.

import os
from certisigma import CertiSigmaClient, CertiSigmaError

# Best practice: load the API key from the environment, never hard-code it.
client = CertiSigmaClient(api_key=os.environ["CERTISIGMA_API_KEY"])

# `batch` is your input: an iterable of (logical_id, sha256_hex) pairs.
# In production this is typically a database query, a CSV reader, or
# the output of a build pipeline.
batch = [
    ("receipt_2026-04-22_001", "a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90"),
    ("receipt_2026-04-22_002", "b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90a1"),
    # ... thousands more ...
]

results = []
for logical_id, hash_hex in batch:
    try:
        att = client.attest_hash(hash_hex)
        results.append((logical_id, att.attestation_id, att.registered_at))
    except CertiSigmaError as exc:
        # Keep the row, mark as failed; reconcile later. Do NOT abort the
        # whole batch on a single error.
        results.append((logical_id, None, str(exc)))

# Persist `results` so a downstream job can poll T1/T2 status — or
# configure a webhook on your enterprise key and skip the polling.

CertiSigma SDK (JavaScript / Node).

The same shape, with native Promise concurrency. Tune the concurrency cap to your contract's rate limit — the SDK will backpressure rather than dropping requests.

import { CertiSigmaClient } from '@certisigma/sdk';

// Best practice: read the API key from the environment, never hard-code it.
const client = new CertiSigmaClient({
  apiKey: process.env.CERTISIGMA_API_KEY,
  // Tune to your contract's burst limit. Conservative defaults below.
  maxConcurrent: 8,
});

const batch = [
  ['receipt_2026-04-22_001', 'a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90'],
  ['receipt_2026-04-22_002', 'b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90a1'],
  // ... thousands more ...
];

const results = await Promise.allSettled(batch.map(async ([logicalId, hashHex]) => {
  const att = await client.attestHash(hashHex);
  return { logicalId, attestationId: att.attestationId, registeredAt: att.registeredAt };
}));

// `results` is an array of { status: 'fulfilled'|'rejected', value|reason }.
// Persist successes, retry rejections with exponential backoff.

Census CLI (no API key, public rate limits).

For one-off batches up to a few hundred hashes, the Census CLI is the fastest path. It uses the same public endpoints as ExistBefore (no API key, public IP-based rate limits) and reads hashes from stdin or a file.

# Pipe a list of SHA-256 hashes (one per line) directly into census attest.
# Public rate limits apply — fine for ad-hoc batches, not for nightly cohorts.
cat hashes.txt | census attest --json > attestations.json

# attestations.json is a JSON array of { hash_hex, attestation_id, registered_at }.
# For larger batches with SLA, switch to the SDK with an enterprise key.

Install: pip install certisigma-census or npm install -g @certisigma/census.

Status callbacks (webhooks).

For enterprise contracts, you can register a webhook URL once. Every time an attestation crosses a tier boundary (T0 → T1, T1 → T2), CertiSigma POSTs a signed payload to your endpoint. Your batch-tracking database receives the update without polling.

# Register a webhook (one-time setup, via the developer portal or the API).
curl -X POST https://api.certisigma.ch/webhooks \
  -H "Authorization: Bearer $CERTISIGMA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-system.example.com/certisigma-webhook",
    "events": ["attestation.t1_confirmed", "attestation.t2_confirmed"]
  }'

# CertiSigma POSTs to your URL with a signed payload:
# {
#   "event": "attestation.t1_confirmed",
#   "attestation_id": "att_...",
#   "hash_hex": "...",
#   "t1": { "timestamp_token": "...", "tsa": "...", "registered_at": "..." }
# }
# Verify the signature with the public key from /keys/{webhook_signing_key_id}.

Next steps.

FAQ

Can I use the same API key for the SDK and the web flow? No. The web flow uses a server-side proxy key managed by ExistBefore. Your SDK key is yours alone — issued to your organization, scoped to attest, and rate-limited per your contract.

Does the SDK store hashes anywhere? No. The SDK only forwards hashes to the API. Persistence (which hashes you've attested, with what logical IDs) is your responsibility — typically a database column next to the original record.

What happens if the network drops mid-batch? The SDK retries with exponential backoff. After the maximum retry budget, the call raises a structured error you can catch and reconcile later. Idempotency: re-attesting the same hash returns the same attestation ID — never a duplicate.

Can I attest 1 million hashes per day? Yes — but not on the public free rate limit. An enterprise contract sizes the rate limit and SLA to your peak load.