Invoance
Get a DemoLog InSign Up
In this article
Resources/How to Export Audit Logs for Enterprise Customers: Signed, Verifiable, Audit-Ready
Compliance·13 min read·June 22, 2026

How to Export Audit Logs for Enterprise Customers: Signed, Verifiable, Audit-Ready

By Adeola Okunola, Founder, Invoance·

Any system can dump audit logs to a CSV. The problem is that a CSV is a file you are asking an auditor to trust. This guide shows how to export audit logs as signed, independently verifiable records: paginated from a real API, with a per-record Ed25519 signature and a gapless sequence that proves nothing was dropped or altered.

Every system can export a CSV. That is the problem.

When an enterprise customer asks whether they can export their audit logs, the question behind the question is rarely "can I get a file." Any system can produce a file. Their security and compliance teams are asking something harder: can we hand this export to an auditor, a regulator, or opposing counsel, and have it hold up when someone has a reason to doubt it.

A CSV or JSON dump from a typical logging stack does not clear that bar. It is a file generated by the vendor, from a database the vendor controls, with timestamps from a clock the vendor can set. Nothing inside the file proves it is complete. Nothing proves a row was not edited, reordered, or quietly removed before the export ran. The auditor is being asked to trust the exporter, which is precisely the thing an audit exists to avoid.

This guide is about exporting audit logs in a form that does not require that trust. The mechanics are straightforward, you ask the API for the records and write them to a file, or let the export endpoint build the file for you. The difference is in what each record carries: a per-tenant Ed25519 signature applied when the event was written, and a gapless sequence number that makes a missing record detectable. The export becomes evidence that verifies itself, instead of a document that depends on your good word.

What enterprise customers actually mean by "export"

Strip away the format debate and three concrete requirements remain. They map almost exactly to what an auditor checks.

Completeness. The export has to represent every event in the requested scope, with a way to detect if any are missing. "Here is a CSV" tells the auditor nothing about what is not in the CSV. A gap has to be detectable from the data itself, not taken on faith.

Integrity. Each record has to be provably unchanged since the moment it was created. Not "we have strict database permissions," that is a statement about your operational controls, which the auditor would then have to audit. The record itself has to carry the proof.

Portability. The auditor has to be able to verify the export without access to your systems and without trusting you or your vendor. If verification requires logging into your dashboard or believing your assurances, it is not independent verification.

Most audit-log exports satisfy none of these. They are point-in-time snapshots whose trustworthiness collapses to "the vendor says so." The rest of this guide walks through producing an export that satisfies all three.

Key insight. An audit-log export is only as useful as it is hard to forge. If the recipient has to trust the exporter, the export has not proven anything, it has just moved the trust problem into a spreadsheet.

Signed-at-write-time changes what an export is

On Invoance, an audit event is signed the instant it is recorded, not when it is exported. When you POST an event to the audit API, the backend assigns it the next sequence number for that organization, canonicalizes it, hashes the canonical bytes, and signs the result with your tenant's own Ed25519 private key. The signature and the hash are stored on the row alongside the data.

Two properties fall out of this. First, every record is independently verifiable for as long as you keep it, the proof travels with the data, so a record pulled out of the system years later still verifies against your published public key. Second, the sequence number is gapless and assigned in order, so a missing record leaves a hole that anyone can see.

That is why an export from Invoance is evidence rather than a snapshot. Whether you request a packaged file or pull events through the API, every record is already signed and sequenced, so what you hand over verifies the moment it lands.

Three-stage flow: an audit event is signed with a per-tenant Ed25519 key when written, exported through the signed API into an NDJSON file, then verified independently by an auditor who checks the signature and a contiguous sequence without trusting the vendor.
An audit-log export on Invoance: signed at write time, exported through the API, and verified independently by the recipient.

The one-call export: request a file, download it when it is ready

For a point-in-time export, the audit API does the assembly for you. POST a scope and a format to /v1/audit/exports and Invoance queues a job, streams every matching event from both hot storage and cold archival into a single file, and returns a download link when it is ready. One request covers the whole range, including events already tiered to cold storage, with no pagination on your side.

The body takes organization_id, a format of csv or ndjson, and an optional filters object with the same fields the query API accepts: actions, actor_id, target_id, range_start, range_end. The call returns an export id with a status of pending.

You then poll GET /v1/audit/exports/{id} until status is ready. The ready response carries a row_count and a download_url, a presigned link valid for 24 hours. CSV exports are written with spreadsheet formula-injection protection, so a cell that begins with =, +, or @ cannot execute when an auditor opens the file in Excel.

POST /v1/audit/exports, then poll until ready
# 1. Queue the export. format is csv or ndjson; filters are optional.
curl -s -X POST https://api.invoance.com/v1/audit/exports -H "Authorization: Bearer $INVOANCE_AUDIT_READ_KEY" -H "Content-Type: application/json" -d '{"organization_id":"aorg_K2Q8Z3X9V7","format":"ndjson","filters":{"range_start":"2026-01-01T00:00:00Z"}}'
# -> 202  { "id": "aexp_01JZ...", "status": "pending", "format": "ndjson" }

# 2. Poll until status is "ready", then download from download_url (presigned, 24h).
curl -s https://api.invoance.com/v1/audit/exports/aexp_01JZ... -H "Authorization: Bearer $INVOANCE_AUDIT_READ_KEY"
# -> 200
# {
#   "id": "aexp_01JZ...",
#   "status": "ready",
#   "format": "ndjson",
#   "row_count": 48213,
#   "error": null,
#   "created_at": "2026-06-26T12:00:00Z",
#   "completed_at": "2026-06-26T12:00:09Z",
#   "download_url": "https://r2.invoance.com/exports/...signed..."
# }

Key insight. One export job covers hot and cold storage in a single file. The worker streams it with bounded memory, so a multi-million-row export does not depend on you writing a careful pagination loop.

Or stream it yourself: page the signed query API

If you would rather stream the export yourself or fold it into a pipeline, page the audit query endpoint directly. GET /v1/audit/events, called with an API key that holds the audit:read scope, returns events newest-first and uses keyset pagination, so you page through an arbitrarily large log without the skips and duplicates that offset-based paging suffers under concurrent writes.

You scope the export with query parameters: organization_id is required, and you can narrow by range_start and range_end (RFC3339 timestamps), by action (a comma-separated allowlist), by actor_id, and by target_id. limit defaults to 50 and caps at 100 per page. Each response carries an events array and a next_cursor; when next_cursor comes back null, you have reached the end of the range.

The loop is the whole export: call the endpoint, write the events, and if next_cursor is set, call again with cursor set to that value. Keep going until it is null.

One page of an export, scoped to an org and a date range
curl -s https://api.invoance.com/v1/audit/events \
  -H "Authorization: Bearer $INVOANCE_AUDIT_READ_KEY" \
  --get \
  --data-urlencode "organization_id=aorg_K2Q8Z3X9V7" \
  --data-urlencode "range_start=2026-01-01T00:00:00Z" \
  --data-urlencode "range_end=2026-04-01T00:00:00Z" \
  --data-urlencode "limit=100"

# Response:
# {
#   "events": [ { "id": "aevt_...", "seq": 4831, "action": "user.role.granted",
#                 "payload_hash": "9f86...a08", "signature": "..." }, ... ],
#   "next_cursor": "3231323..."   // null on the last page
# }

Key insight. Keyset pagination matters for exports specifically: new events always sort onto the first page, so paging backward in time under live writes never skips or double-counts a record. An offset-based export can silently drop rows the moment the log is written to mid-export.

The same export, as a script

In practice you wrap that one call in a loop and stream the results to a file. NDJSON, one JSON object per line, is the friendliest format to hand an auditor: every line is a complete, independently verifiable record, and the file streams without ever holding the whole log in memory.

The pattern below is the entire export. It writes each page as it arrives and follows next_cursor until the API reports there is nothing left. The same shape works in any language, the logic is request, append, advance the cursor, repeat.

Node: stream a full export to NDJSON
import { writeFile, appendFile } from "node:fs/promises";

const BASE = "https://api.invoance.com/v1/audit/events";
const KEY = process.env.INVOANCE_AUDIT_READ_KEY;

async function exportAuditLog(orgId, outFile) {
  await writeFile(outFile, "");
  let cursor = null;
  let total = 0;

  do {
    const url = new URL(BASE);
    url.searchParams.set("organization_id", orgId);
    url.searchParams.set("limit", "100");
    if (cursor) url.searchParams.set("cursor", cursor);

    const res = await fetch(url, { headers: { Authorization: "Bearer " + KEY } });
    if (!res.ok) throw new Error("export failed: HTTP " + res.status);

    const page = await res.json();
    // One signed record per line (NDJSON).
    const lines = page.events.map((e) => JSON.stringify(e)).join("\n");
    if (lines) await appendFile(outFile, lines + "\n");

    total += page.events.length;
    cursor = page.next_cursor;
  } while (cursor);

  return total;
}

const n = await exportAuditLog("aorg_K2Q8Z3X9V7", "events.ndjson");
console.log("exported " + n + " signed audit records");

Verify every record's signature

A signed export is only worth something if the recipient can check the signatures, so the audit API exposes verification as a first-class endpoint. GET /v1/audit/events/{id}/verify takes a single event, rebuilds the exact canonical bytes that were signed from the stored columns, recomputes the hash, and checks the Ed25519 signature against your tenant's registered public key. Not the key stored on the row, the key pinned in your tenant record, which is what makes a forged or swapped key fail rather than pass.

The response is unambiguous: valid is a boolean, reason names the failure when valid is false (for example payload_hash_mismatch), and the body echoes the schema, the payload_hash it computed, and key_source so the verifier knows exactly which key was used.

For an export, you run this across the file. A practical pattern is to verify every record on the way out, or verify a random sample plus every record above a sensitivity threshold, and attach the results to the export as a manifest. Either way the point stands: the auditor does not have to take the file on faith, and neither do you. Because the signature is Ed25519 and your tenant's public key is publishable, a recipient can also verify the whole export offline, with a standard crypto library, without ever calling Invoance.

GET /v1/audit/events/{id}/verify → 200
{
  "event_id": "aevt_01JZ8M4K7Q2W9R3X5Y6Z7A8B9C",
  "valid": true,
  "reason": null,
  "schema": "invoance.audit/1",
  "payload_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "signed_data": {
    "action": "user.role.granted",
    "actor": { "type": "user", "id": "u_42" },
    "event_id": "aevt_01JZ8M4K7Q2W9R3X5Y6Z7A8B9C",
    "ingested_at": "2026-03-14T09:21:00.512Z",
    "occurred_at": "2026-03-14T09:21:00.000Z",
    "org_id": "aorg_K2Q8Z3X9V7",
    "seq": 4831,
    "targets": [ { "type": "user", "id": "u_19" } ]
  },
  "key_source": "tenant_keys"
}
See it in action
  • Why traditional audit logs fail under regulatory scrutiny— The longer argument for why database-backed logs do not survive adversarial examination, and what cryptographic proof changes.

Prove the export is complete

Verification tells you each record you exported is authentic. It does not, on its own, tell you that you exported all of them. Completeness is a separate question, and it is the one a CSV can never answer.

This is what the gapless sequence number is for. Every audit event for an org carries a strictly increasing seq, assigned in a single transaction at write time. GET /v1/audit/orgs/{id}/integrity walks that sequence and reports whether it is contiguous: it echoes the from/to window it scanned, last_seq, a count and the expected count, a contiguous boolean, and a gaps array listing any missing ranges. A complete export covers a contiguous block of sequence numbers; a hole in the middle is mathematically visible.

Run the integrity scan as part of the export and record its result. Together with the per-record signatures, it lets you make a precise, defensible claim: these are records 1 through last_seq for this organization, each one signed and unaltered, with no gaps.

GET /v1/audit/orgs/{id}/integrity → 200
{
  "org_id": "aorg_K2Q8Z3X9V7",
  "from": 1,
  "to": 48213,
  "last_seq": 48213,
  "evacuated_through_seq": 0,
  "count": 48213,
  "expected": 48213,
  "contiguous": true,
  "gaps": [],
  "gaps_truncated": false,
  "note": "A hole below the high-water mark proves deletion. An attacker who also resets the per-org seq counter can hide truncation of the newest events; only the Tier-3 signed checkpoint closes that. Integrity is 'Signed', not 'complete', until it ships."
}

Key insight. Be precise about what the integrity scan proves. A gap in the sequence reveals a modified or deleted record in the middle of the log. Detecting truncation of the very newest events requires a signed checkpoint of the sequence head. Claim what the math supports and nothing more, overclaiming integrity is how a trust product loses trust.

Set the retention window your contracts require

Enterprise audit-log requirements almost always include a retention term, "logs retained for N years, available on request." On Invoance, retention is set per organization with PUT /v1/audit/orgs/{id}/retention, and the window you can request is bounded by your plan tier. Higher tiers unlock longer retention; the API clamps any request to your plan's cap rather than silently accepting a value it cannot honor.

Behind the endpoint, recent events stay hot in Postgres for fast querying, and older events are tiered to dedicated cold object storage as compressed, signed segments. The signature and sequence number travel with the data into cold storage, so an event exported from a three-year-old cold segment verifies exactly the same way as one written this morning. Retention changes where a record lives, never whether it can still be proven.

Set a 365-day retention window for an org
curl -s -X PUT https://api.invoance.com/v1/audit/orgs/aorg_K2Q8Z3X9V7/retention \
  -H "Authorization: Bearer $INVOANCE_AUDIT_WRITE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"days": 365}'

Stream events to your SIEM as they happen

Security teams usually want audit events flowing into their own tooling continuously, not just on request. Attach a streaming destination to an org with POST /v1/audit/orgs/{id}/streams. The supported destination today is a generic HTTPS webhook, which covers most SIEM and log pipelines; named connectors such as Splunk HEC are on the connector roadmap.

Every delivery is signed with an HMAC secret carried in the X-Invoance-Signature header. That secret is returned exactly once, when you create the stream, so the receiver can confirm a payload came from Invoance and was not altered in transit. A new stream starts at the org's current sequence and forwards events from the moment you connect it rather than replaying history, which is how SIEM pipelines expect to behave.

The destination is checked against server-side request forgery before it is accepted: it must be HTTPS and cannot resolve to a private, loopback, link-local, or cloud-metadata address. For a product whose whole point is trust, an audit stream must never become a way to probe an internal network.

POST /v1/audit/orgs/{id}/streams
curl -s -X POST https://api.invoance.com/v1/audit/orgs/aorg_K2Q8Z3X9V7/streams -H "Authorization: Bearer $INVOANCE_AUDIT_WRITE_KEY" -H "Content-Type: application/json" -d '{"type":"webhook","url":"https://collector.example.com/invoance"}'

# 201 Created  (signing_secret is shown ONCE; store it now)
# {
#   "id": "astr_01JZ...",
#   "type": "webhook",
#   "endpoint": "https://collector.example.com/invoance",
#   "state": "active",
#   "cursor_seq": 4831,
#   "signing_secret": "whsec_b3f1a9c4..."
# }

Key insight. Verify the X-Invoance-Signature HMAC on every webhook delivery, the way you would a Stripe webhook. Without that check, anyone who learns your collector URL could post forged audit events into your SIEM.

Hand your auditor a hosted, read-only view

Sometimes the cleanest export is not a file, it is a link. POST /v1/audit/portal_sessions mints a one-time link to a hosted, read-only viewer scoped to a single org. You give it to an auditor or a customer's security reviewer and they browse the log, filter by action, actor, and date, open any event, and verify its signature from the browser, with no Invoance account and no access to anything else in your tenant.

The link is single-use and must be opened within five minutes; once opened it becomes a read-only session whose length you choose, from one minute up to 24 hours. The viewer can export the filtered view to CSV or JSON itself, so the reviewer can take a copy without you running anything. Pass an intent of audit_logs for the log viewer, or log_streams to let a customer manage their own webhook destinations.

POST /v1/audit/portal_sessions
curl -s -X POST https://api.invoance.com/v1/audit/portal_sessions -H "Authorization: Bearer $INVOANCE_AUDIT_WRITE_KEY" -H "Content-Type: application/json" -d '{"organization_id":"aorg_K2Q8Z3X9V7","intent":"audit_logs","session_duration_seconds":3600,"link_duration_seconds":300}'

# 201 Created
# {
#   "id": "aps_01JZ...",
#   "intent": "audit_logs",
#   "url": "https://app.invoance.com/portal?token=...",
#   "token": "...",               // one-time, shown once
#   "link_expires_in": 300,       // seconds to open the link
#   "session_expires_in": 3600    // viewer session length once opened
# }

What is still on the roadmap

Two things are deliberately not claimed yet, and it is worth being exact about them with a security team.

First, more named SIEM connectors. The generic signed webhook ships today; dedicated connectors such as Splunk HEC are still on the roadmap, so a destination that needs a vendor-specific format sits behind the webhook for now.

Second, and more important: the integrity scan proves that nothing in the middle of the log was changed or removed, but detecting truncation of the very newest events requires a signed checkpoint of the sequence head, which is not shipped yet. That is exactly why Invoance describes these logs as signed and verifiable rather than immutable. Per-event signatures and a gapless sequence are strong, provable claims today; a tamper-proof head that also rules out tail-truncation is the next step. Claiming only what the cryptography supports today is the entire point of a trust product.

From signup to your first verifiable export

The fastest way to evaluate this is to produce a real export against a free account. Sign up, create an audit organization, and mint an API key with the audit:read and audit:write scopes. Write a handful of events with the ingest endpoint, then request an export with the calls above, you get back records that are actually signed with your tenant's own key, with a real sequence and a real integrity scan.

The records your free account produces verify through the same endpoints and against the same kind of per-tenant key that back Compliance and Enterprise accounts. The infrastructure is identical; the plan tiers change limits and retention, not whether a record can be proven.

If you are working through an enterprise security review, that free export is the asset to hand over. Give the reviewer an exported record and the verify endpoint, let them confirm the signature and the contiguous sequence themselves, and the trust question stops being a conversation and becomes a demo. That is usually what unblocks the deal.

See it in action
  • Create a free account— Sign up, create an audit organization, and generate audit:read / audit:write API keys in a few minutes.
  • Audit logs API reference— The ingest, export, verify, integrity, streaming, and portal endpoints used throughout this guide.
  • Audit Logs product overview— What the product does, who it is for, and how it helps you pass an enterprise security review.
  • Compare plans and retention limits— Compare plans and retention windows across tiers.

Signed, independently verifiable activity logs you can embed, stream, and export, one API call per event.

Start freeAudit LogsDiscuss your use case
Adeola Okunola
Adeola Okunola

Founder, Invoance

About the author

I'm Adeola, founder of Invoance. I build proof infrastructure for audit logs, AI attestations, and business records that need to stand up to security, compliance, and legal scrutiny. Most systems document what happened. Invoance helps prove it.

All articles by Adeola

Recommended

Compliance·7 min read

Why Traditional Audit Logs Fail Under Regulatory Scrutiny

Your application logs record what happened. But in an audit or legal proceeding, the first question is not what your logs say, it is whether anyone can trust your logs. Traditional logging has a fundamental integrity problem that most teams do not address until it is too late.

Read
Product·10 min read

Event Ledger: Immutable Compliance Records for Business Events

Logs can be edited. Databases can be modified. The Event Ledger is different, every event is hashed with SHA-256, signed with Ed25519, and stored in an append-only ledger that cannot be altered after ingestion.

Read
Compliance·12 min read

SOC 2 Compliance: The Complete Guide for Modern Organizations

SOC 2 has become the baseline trust standard for SaaS companies and service providers. This guide covers the trust service criteria, audit types, preparation strategies, and how verifiable evidence closes the gap between controls and proof.

Read
Compliance·11 min read

HIPAA Compliance: The Guide for Technology Organizations

HIPAA governs how protected health information is handled across healthcare and technology. This guide covers what technology organizations need to know about HIPAA requirements, common pitfalls, and how verifiable evidence strengthens compliance posture.

Read

How to Export Audit Logs for Enterprise Customers: Signed, Verifiable, Audit-Ready

A practical guide to exporting audit logs for enterprise customers and their auditors. Learn how to request a packaged CSV or NDJSON export, page the signed API yourself, stream events to your SIEM, verify every record's Ed25519 signature, and prove an export is complete with an integrity scan, so the export is cryptographic proof, not a CSV anyone has to trust.

Category: Compliance. Published 2026-06-22 by Adeola Okunola, Founder, Invoance. Tags: Audit Logs, Audit Log Export, Enterprise, Compliance, SIEM, Tamper Evidence, Ed25519, Audit Trail, API.

01Audit logs02AI decisions03Documents04Business events05Whole workflows
Invoance

Neutral proof infrastructure for records that must survive scrutiny. Signed at creation. Verifiable outside your dashboard.

ALL SYSTEMS OPERATIONALEvidence infrastructure · Online

Build

  • Developer overview
  • API endpoints
  • Official SDKs
  • Authentication
  • Verification model

Use Invoance

  • Why Invoance
  • How it works
  • Compliance teams
  • Finance teams
  • Pricing

Verify

  • Audit log
  • AI attestation
  • Document
  • Ledger event
  • Sealed trace

Company

  • Help center
  • Resources
  • Security
  • Partners
  • Contact
  • System status
FIELD NOTES / 01Proof patterns for teams building trust.

Invoance provides cryptographic proof and verification infrastructure. It does not provide legal, financial, compliance, or regulatory advice.

Read proof disclaimer

Records anchored with Invoance are cryptographically signed and designed to reveal tampering. Invoance verifies that a specific record existed in a particular form at a particular time; it does not assess the record's accuracy, authenticity, legality, or underlying contents. Public verification links can be resolved without authentication. Invoance is not a custodian of funds, a legal authority, or a regulated financial institution. Using Invoance does not by itself satisfy any legal or regulatory requirement. Consult qualified legal or compliance professionals regarding your obligations.

© 2025 – 2026 Invoance, Inc. All rights reserved.
PrivacyLegalFAQ
PROOF, NOT PROMISES.