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.
| Type | Use Case |
|---|
payment_receipt | Payments, invoices, refunds |
security_event_receipt | Auth events, key rotations, anomalies |
delivery_receipt | Document delivery, notifications |
compliance_receipt | KYC/AML checks, regulatory evidence |
custom_receipt | Any 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
Choose a receipt type
Call GET /v1/receipt-types to list available types and inspect the schema for required fields.
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.
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..."
}
}'
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:
| Event | Triggered When |
|---|
receipt.created | A receipt is successfully minted |
receipt.revoked | A 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
| Verdict | Meaning |
|---|
VALID | Signature is valid, key is active, receipt is not revoked |
REVOKED | Receipt has been explicitly revoked |
INVALID_SIGNATURE | Cryptographic signature verification failed |
KEY_COMPROMISED | Signing key was marked compromised at or before issuance |
KEY_INACTIVE | Signing key is no longer active |
NOT_FOUND | Receipt 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
| Field | Type | Description |
|---|
q | string | Full-text search across payload content |
receipt_type | string | Filter by type name |
status | string | active, revoked, superseded, or redacted |
issuer_id | UUID | Filter by issuer |
from_date | RFC 3339 | Start of date range |
to_date | RFC 3339 | End of date range |
index_key | string | Indexed payload field name |
index_value | string | Value to match for index_key |
limit | integer | Max results (1–100, default 20) |
offset | integer | Pagination 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
| Status | Meaning |
|---|
pending | Queued, not yet started |
running | Processing records |
complete | Done — download_url is available |
failed | Error — 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:
| Policy | Duration | Description |
|---|
standard | 2 years | Default. Suitable for most transactional receipts. |
extended | 7 years | For compliance use cases requiring longer retention. |
permanent | Never | Never 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.