Skip to main content

Event Envelope

Every event in Trailproof uses the same 10-field envelope. Your domain-specific data goes in payload — Trailproof handles the rest.
Trailproof doesn’t validate or inspect payload contents. It stores them opaquely. Your application is responsible for payload structure.

TrailEvent Fields

FieldTypeRequiredDescription
event_idstringyesUUID v4, auto-generated by Trailproof
event_typestringyesNamespaced type (e.g., myapp.user.login)
timestampstringyesISO-8601 UTC, auto-generated by Trailproof
actor_idstringyesWho performed the action
tenant_idstringyesTenant/org isolation key
trace_idstringnoCross-system correlation ID
session_idstringnoSession grouping ID
payloadobjectyesDomain-specific data (opaque to Trailproof)
prev_hashstringyesHash of the previous event
hashstringyesSHA-256 hash of this event
signaturestringnoHMAC-SHA256 signature (if signer configured)

Auto-Generated vs. Caller-Provided

You provide (required):
  • event_type — what happened
  • actor_id — who did it
  • tenant_id — which tenant (use "default" for single-tenant)
  • payload — domain-specific data
You can optionally provide:
  • trace_id — correlate events across systems
  • session_id — group events within a session
Trailproof auto-generates:
  • event_id — UUID v4
  • timestamp — ISO-8601 UTC
  • prev_hash — from the hash chain
  • hash — SHA-256 of the event
  • signature — if a signing key is configured

Event Type Convention

Event types follow a namespaced pattern: {project}.{domain}.{action}
myapp.user.login
myapp.user.logout
memproof.memory.write
memproof.memory.redact
attesta.approval.requested
attesta.approval.decision
Trailproof doesn’t enforce this convention — event types are just strings. The naming pattern is a recommendation for consistency.

Example

from trailproof import Trailproof

tp = Trailproof()

event = tp.emit(
    event_type="myapp.user.login",
    actor_id="user-42",
    tenant_id="acme-corp",
    payload={"ip": "1.2.3.4", "method": "oauth"},
    trace_id="trace-abc",
    session_id="session-xyz",
)

# Access all fields
print(event.event_id)     # "f47ac10b-..."
print(event.event_type)   # "myapp.user.login"
print(event.timestamp)    # "2025-01-15T10:30:00Z"
print(event.actor_id)     # "user-42"
print(event.tenant_id)    # "acme-corp"
print(event.trace_id)     # "trace-abc"
print(event.session_id)   # "session-xyz"
print(event.payload)      # {"ip": "1.2.3.4", "method": "oauth"}
print(event.prev_hash)    # "0000...0000" (genesis)
print(event.hash)         # "a1b2c3..."

Validation

Trailproof validates required fields on emit(). Missing or empty required fields throw a ValidationError:
Python
# These all throw ValidationError:
tp.emit(event_type="", actor_id="user-42", tenant_id="acme", payload={})
tp.emit(event_type="app.action", actor_id="", tenant_id="acme", payload={})
tp.emit(event_type="app.action", actor_id="user-42", tenant_id="", payload={})
All four required caller fields — event_type, actor_id, tenant_id, and payload — must be non-empty. Trailproof raises ValidationError immediately on empty or missing values.

Next Steps

Hash Chain

How events are cryptographically linked.

API Reference

Full API documentation for both SDKs.