Sigilbase is in early access, free while we're in beta. Your data and evidence are permanent.

Writing

Audit event ingestion API: integration best practices

Integrating an audit event ingestion API looks like an afternoon of work: serialise the event, POST it, done. The integrations that hold up under an auditor's sampling, a regulator's timeline, or a dispute differ from the afternoon version in ways that are invisible until something fails: they capture events exactly once, in order, without blocking the product, without leaking secrets into permanent records, and they notice when delivery stops. This guide covers the practices that make the difference, for the engineers wiring it in and the platform teams setting the standard.

It pairs with our guides on what the frameworks require from audit logs and implementing provable audit logs; this one is about the producer side, getting events into an ingestion API reliably.

Capture events where the decision happens

Emit the audit event from the application code that performs the action, not from a parser reconstructing intent out of web server logs or database triggers guessing at attribution. Only the application knows the business meaning, "invoice voided, reason duplicate, approved by this user", and only the application can attribute the action to the acting human rather than to the connection pool's service account. Service-account attribution is among the most common audit findings, and it is unfixable after the fact: a year of events recorded as svc:api cannot be re-attributed later. Carry the acting identity through internal service hops explicitly, so the system of record at the end of the chain still knows who started it.

Capture at the point of intent also settles what to record: the catalogue of auditable actions is the catalogue of decisions your application makes that anyone might later need to reconstruct, which is a product question to settle deliberately rather than a byproduct of whatever happened to get logged. The rule holds when the actor is an automated system rather than a person; recording what an agent saw and decided is its own discipline, covered on the AI decision logging page.

Design the event schema before the first integration

Audit events are write-once and long-lived, so the schema is close to permanent and deserves an hour of design before any code. The stable core: actor, action as a namespaced dotted name such as user.role.granted, the resource affected, outcome, UTC timestamp from a synchronised clock, origin, and a payload for action-specific detail. Version the schema explicitly on every event from day one, because version two is inevitable and unmarked version one records are forever.

Keep the action vocabulary controlled. A finite, documented set of action names is what makes the log queryable and reviewable; free-text action strings produce a log that can only be read, never analysed. And decide centrally what may never enter a payload: raw secrets, card data, and personal information that a deletion request may later cover have no place in records designed to be unalterable. Where you must prove a value was present without storing it, store its hash. Enforcement belongs at the producer, in the shared library that builds events, because scrubbing an immutable log afterwards is by design not possible.

Make every write idempotent

Networks fail after the server accepted the write and before the client heard about it, which forces a choice: retry and risk duplicates, or do not and risk gaps. An audit trail can afford neither, and idempotency keys dissolve the dilemma.

An idempotency key is a producer-supplied unique identifier on each event; the ingestion API stores it and treats any subsequent write bearing the same key as a duplicate to acknowledge and discard, making retries safe.

The subtlety is where the key comes from. Derive it deterministically from the business action, the aggregate and its change, not randomly per request attempt, because a producer that crashes after committing the action and restarts must generate the same key on its second attempt for deduplication to save you. With deterministic keys in place, the delivery policy becomes simple and aggressive: retry until acknowledged.

Decouple delivery with an outbox

The write to the ingestion API should not sit synchronously inside your request path: an API outage would become your product's outage, and latency would be added to every audited action. But the naive alternative, firing the event from a background thread after the response, silently loses events on every crash, deploy, and process recycle, and loses ordering under concurrency.

The pattern that gives you both safety and decoupling is the transactional outbox. The application writes the audit event into an outbox table in the same database transaction as the business change, so the event exists if and only if the action committed. A separate relay reads the outbox in order, delivers to the ingestion API, and marks rows done on acknowledgement. Crashes lose nothing, since undelivered events wait in the outbox; ordering is preserved per stream; and the product's availability depends on its own database, not on the audit endpoint. If your architecture already runs on a durable queue with the event written transactionally, the same properties hold. What matters is the invariant: no committed action without a durably captured event.

Batch, and retry with backoff

Deliver in batches: audit APIs, Sigilbase's included, accept batched writes precisely so that a chatty system does not pay a round trip per event, and the outbox relay is the natural place to accumulate them. Size batches modestly, respect the API's documented limits, and treat a batch as delivered only when acknowledged.

Retries follow the standard discipline, exponential backoff with jitter, honouring any rate-limit signals the API returns rather than hammering through them. Because every event carries a deterministic idempotency key, the relay retries without fear: the worst a duplicate delivery can produce is an acknowledgement. What backoff must never become is give-up; after the retry budget, events remain in the outbox and the failure escalates to the next section rather than to /dev/null.

Treat delivery failure as an incident

An audit trail that stops silently fails at the exact moment it exists for, and under PCI DSS a logging failure is explicitly an event class to detect and respond to. Instrument the pipeline as production infrastructure: outbox depth and oldest-undelivered age, delivery error rates, and end-to-end lag from action to acknowledged event, with alerts on sustained growth in any of them. Reconcile continuously rather than trusting the plumbing, comparing counts of auditable actions against accepted events, and alerting on gaps. The producers' silence deserves its own alarm: a service that normally emits events and has emitted none for an hour is more likely broken than idle. None of this is exotic; it is the same operational care your payment path gets, applied to the system whose job is remembering what the payment path did.

Test it like it will be audited

The failure modes above only stay theoretical if you rehearse them. Kill the relay mid-batch and verify nothing drops or duplicates. Sever the network to the ingestion API for an hour under load and watch the backlog drain cleanly afterwards. Crash the producer between the business commit and first delivery attempt and confirm the outbox saves the event. Replay a day of production-shaped traffic and reconcile counts to zero difference. Then keep a thin version of the reconciliation running permanently in production, because completeness is precisely what an auditor samples for, and the integrations that pass are the ones that measured it before anyone asked.

The checklist

Emit from the application at the point of intent, with the acting human attributed through every hop. Fix the schema and action vocabulary first, version from day one, and keep secrets and deletable personal data out of payloads. Put a deterministic idempotency key on every event. Capture through a transactional outbox, deliver in batches with backoff and jitter, and never give up on an unacknowledged event. Alert on backlog, lag, gaps, and silence. Fault-inject in testing and reconcile in production.

Get the producer side right and the value compounds with what sits behind the API: an ingestion pipeline that never loses an event, feeding a log that can prove no event was altered, is an audit trail that stands up end to end. For what the frameworks will ask of the result, see our guide to audit log requirements; for the concrete endpoints, batch limits and error semantics when integrating with Sigilbase, see the API documentation.

Sigilbase turns audit logs into provable evidence. Start free and record provable history from the first event.

FAQ

Frequently asked questions

What is an idempotency key in audit event ingestion?

A unique identifier the producer attaches to each event so that retries after a timeout or network failure cannot create duplicate records. The ingestion API deduplicates on the key, making it safe to retry aggressively. Derive the key deterministically from the business action itself, not randomly per request attempt, so a crashed-and-restarted producer still deduplicates correctly.

Should audit logging block the user's action?

The write to the audit API should not sit synchronously in the request path, because an ingestion outage would then take your product down with it. Record the event durably and locally in the same transaction as the action, then deliver it asynchronously. What must never happen is the action succeeding without the event being durably captured somewhere.

What is the outbox pattern for audit events?

The application writes the audit event to a local outbox table inside the same database transaction as the business change, then a separate relay reads the outbox and delivers events to the ingestion API with retries. It guarantees the event is captured if and only if the action committed, survives crashes and outages, and preserves ordering, which fire-and-forget background threads do not.

What should happen when the audit ingestion API is unreachable?

Events queue durably at the producer and delivery retries with exponential backoff and jitter. Nothing is dropped, the backlog is monitored, and sustained delivery failure pages someone, because an audit trail silently stopping is an incident, and under PCI DSS logging failure is explicitly an event class that must be detected and responded to.

What data should not go into audit event payloads?

Raw secrets, credentials, card numbers, and personal data that privacy law may later require you to delete, since audit records are designed to be unalterable and long-lived. Record identifiers and hashes instead, storing a hash of a sensitive value when you need to prove what it was without holding it. Minimisation at the producer is the only reliable place to enforce this.

How do you verify an audit integration is complete?

Reconcile, do not assume. Count auditable actions on the producer side and compare against accepted events, alert on sequence gaps and on delivery lag, and fault-inject in testing by killing the relay mid-batch and severing the network to prove nothing drops or duplicates. Completeness is the property auditors sample for, and it only holds if it is measured.

Start recording provable history

Chained, sealed, independently verifiable audit logs, from the first event. Free while Sigilbase is in beta.

Start free

Questions first? Write to hello@sigilbase.io.

Privacy

This site runs no analytics and no trackers.

The site itself collects nothing. If you create a Sigilbase account, the data that involves is described in the privacy policy.

To have your email removed, contact hello@sigilbase.io.

Read the full privacy policy

Last updated July 2026