Skip to main content

What Are Receipts?

Receipts are signed, immutable records of discrete events — payments, security incidents, compliance checks, deliveries, and more. Unlike logs or audit trails, every receipt is:
  • Cryptographically signed with your issuer key (Ed25519 or ES256)
  • Anchored in the transparency log with a Merkle inclusion proof
  • Offline-verifiable via proof bundle export
  • Idempotent — the same event always produces the same receipt

Receipt types

Platform-defined receipt types are available to all tenants. Each type has a JSON Schema that the payload field is validated against before signing.
TypeUse Case
payment_receiptPayments, invoices, refunds
security_event_receiptAuth events, key rotations, anomalies
delivery_receiptDocument delivery, notifications
compliance_receiptKYC/AML checks, regulatory evidence
custom_receiptAny other event with open schema

Discovering available types

Call GET /v1/receipt-types to list every type your tenant can use, or GET /v1/receipt-types/{name} to fetch a single type and inspect its JSON Schema.
const types = await client.receipts.listTypes();

// Inspect a specific type's schema
const paymentType = await client.receipts.getType("payment_receipt");
console.log(paymentType.schema.required); // ["amount", "currency", "provider", ...]

Minting a receipt

1

Choose a receipt type

Call GET /v1/receipt-types to list available types and inspect the schema for required fields.
2

Build your payload

Construct the payload object matching the required fields for your receipt type. For payment_receipt, you need amount, currency, provider, provider_reference, and subject.
3

Mint via API

curl -X POST https://api.truthlocks.com/v1/receipts \
  -H "X-API-Key: tl_live_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "issuer_id": "<your-issuer-id>",
    "kid": "ed-key-2026",
    "alg": "Ed25519",
    "receipt_type": "payment_receipt",
    "subject": "customer@example.com",
    "payload": {
      "amount": 5000,
      "currency": "USD",
      "provider": "stripe",
      "provider_reference": "ch_3Px..."
    }
  }'
4

Store the receipt_id

The response includes the receipt_id and log.leaf_index. Store both for future verification.

SDK Examples

const receipt = await client.receipts.mint({
  issuer_id: 'your-issuer-id',
  kid: 'ed-key-2026',
  alg: 'Ed25519',
  receipt_type: 'payment_receipt',
  subject: 'customer@example.com',
  payload: {
    amount: 5000,
    currency: 'USD',
    provider: 'stripe',
    provider_reference: 'ch_3Px...'
  }
});

console.log(receipt.receipt_id, receipt.log.leaf_index);

Listing receipts

const receipts = await client.receipts.list({
  receipt_type: "payment_receipt",
  status: "active",
  limit: 20,
});

Retrieving a receipt

const receipt = await client.receipts.get("receipt-uuid");

Webhook events

Subscribe to receipt events by configuring a webhook endpoint in the console:
EventTriggered When
receipt.createdA receipt is successfully minted
receipt.revokedA receipt is revoked

Revoking a receipt

Revocation is permanent and recorded in the transparency log. Receipts with status revoked remain queryable and their revocation timestamp is included in proof bundles. You can include an optional reason to record why the receipt was revoked.
curl -X POST https://api.truthlocks.com/v1/receipts/{id}/revoke \
  -H "X-API-Key: tl_live_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Duplicate charge refunded"}'
await client.receipts.revoke("receipt-uuid", {
  reason: "Duplicate charge refunded",
});

Verifying a receipt

Use the verify endpoint to check a receipt’s cryptographic validity and current status. The API checks the signature, key status, and revocation state, then returns a verdict.
curl -X POST https://api.truthlocks.com/v1/receipts/verify \
  -H "X-API-Key: tl_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "receipt_id": "receipt-uuid" }'
const result = await client.receipts.verify("receipt-uuid");

if (result.verdict === "VALID") {
  console.log("Receipt is valid");
}

Verdicts

VerdictMeaning
VALIDSignature is valid, key is active, receipt is not revoked
REVOKEDReceipt has been explicitly revoked
INVALID_SIGNATURECryptographic signature verification failed
KEY_COMPROMISEDSigning key was marked compromised at or before issuance
KEY_INACTIVESigning key is no longer active
NOT_FOUNDReceipt not found for this tenant

Proof bundles

A proof bundle is a self-contained package for offline verification. It includes the signed receipt envelope, Merkle inclusion proof from the transparency log, and the issuer key snapshot at issuance time. After downloading a proof bundle, you can verify the receipt without making any further API calls.
const bundle = await client.receipts.getProofBundle("receipt-uuid");

console.log(bundle.bundle_version);          // "receipt-v1"
console.log(bundle.transparency_log);        // Merkle inclusion proof
See the proof bundle API reference for the full bundle structure.

Searching receipts

Search across your receipts using full-text search and faceted filters. The search index supports queries against payload content, and you can narrow results by type, status, date range, or indexed payload fields.
curl -X POST https://api.truthlocks.com/v1/receipts/search \
  -H "X-API-Key: tl_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "q": "stripe",
    "receipt_type": "payment_receipt",
    "from_date": "2026-01-01T00:00:00Z",
    "limit": 20
  }'
const results = await client.receipts.search({
  q: "stripe",
  receipt_type: "payment_receipt",
  from_date: "2026-01-01T00:00:00Z",
  limit: 20,
});

for (const r of results.items) {
  console.log(r.receipt_id, r.receipt_type);
}

Search filters

FieldTypeDescription
qstringFull-text search across payload content
receipt_typestringFilter by type name
statusstringactive, revoked, superseded, or redacted
issuer_idUUIDFilter by issuer
from_dateRFC 3339Start of date range
to_dateRFC 3339End of date range
index_keystringIndexed payload field name
index_valuestringValue to match for index_key
limitintegerMax results (1–100, default 20)
offsetintegerPagination offset

Exporting receipts

Queue an asynchronous bulk export of receipts as JSON or CSV. The API returns immediately with a job ID. Poll the export status endpoint until status is complete, then download from the pre-signed URL.
// Start an export
const job = await client.receipts.export({
  format: "json",
  filters: {
    receipt_type: "payment_receipt",
    status: "active",
  },
});

console.log(job.id, job.status); // "pending"

// Poll for completion
const completed = await client.receipts.getExport(job.id);
if (completed.status === "complete") {
  console.log("Download:", completed.download_url);
}

// List all export jobs
const exports = await client.receipts.listExports();

Export status values

StatusMeaning
pendingQueued, not yet started
runningProcessing records
completeDone — download_url is available
failedError — see error_message

Redacting receipts

Redaction permanently removes PII from a receipt’s payload while preserving the cryptographic proof. The receipt’s signature, transparency log entry, and Merkle inclusion proof remain intact — only the payload_json is replaced with a redaction marker. Use this for GDPR right-to-erasure requests on receipts containing personal data.
await client.receipts.redact("receipt-uuid");
Redaction is permanent. The original payload cannot be restored. The cryptographic proof remains valid for audit purposes.
After redaction:
  • status changes to redacted
  • payload_json is replaced with {"redacted": true, "redacted_by": "tenant_request"}
  • A RECEIPT_REDACT event is anchored in the transparency log
  • All other fields (signature, log proof, receipt type) are preserved

Retention policy

Receipts are automatically cleaned up based on the retention policy set at mint time. A background worker sweeps expired receipts on a regular schedule. Set retention_policy when minting:
PolicyDurationDescription
standard2 yearsDefault. Suitable for most transactional receipts.
extended7 yearsFor compliance use cases requiring longer retention.
permanentNeverNever automatically deleted.
Receipts approaching their retention deadline can be redacted instead of deleted if you need to preserve the cryptographic proof while removing payload data.