Skip to content

Audit log

For developers

Every change to an organisation’s data is an audit entry. Entries are numbered within the organisation’s chain and each carries the hash of the entry before it and a hash of its own content, so a gap or an altered entry is detectable. This page covers the shapes a developer meets: the streamed entry, the export file, and the public key.

Streams and exports carry the same JSON shape.

{
"seq": 4821,
"id": "2f3c9a44-3b1e-4a4f-9e52-7f1d6f1a2c0b",
"at": "2026-11-03T02:14:09.311Z",
"organisationId": "7e0d9c3a-2d8b-4e57-8f43-5c3a1b2d9e10",
"actor": { "userId": "c2b1…", "label": "Mere Ngata" },
"entityType": "card_holder",
"entityId": "9d5e…",
"action": "update",
"changes": { "department": { "from": "Nursing", "to": "Midwifery" } },
"ipAddress": "203.0.113.9",
"userAgent": "Mozilla/5.0 …",
"redactedAt": null,
"prevHash": "a1f0…",
"hash": "7c2e…"
}

seq is the entry’s place in the organisation’s chain, starting at 1 with no gaps. prevHash is the hash of entry seq - 1. redactedAt is set when the personal fields inside changes were blanked under a privacy request or the retention policy; the entry’s place and hash are kept, and the redaction is itself an entry.

Under Settings, Audit log, Streams an organisation sends entries to its own collector as they are written. A cursor per stream means a collector that was down receives everything it missed, in order, once.

Webhook. A POST to your HTTPS URL with up to 200 entries per request:

{ "type": "audit.entries", "organisationId": "7e0d…", "entries": [ … ] }

The request carries X-IDPortal-Event: audit.entries and an X-IDPortal-Signature header computed with the stream’s secret exactly as for every other webhook. Answer with any 2xx status.

Syslog. RFC 5424 over TLS with octet counting (RFC 6587), one message per entry: facility 13 (log audit), severity 6, app name idportal, the action as the message id, structured data [cardoffice@58001 seq="4821" hash="7c2e…" org="7e0d…"], and the entry’s JSON as the message. Your collector must present a certificate that a public resolver trusts.

Settings, Audit log, Export downloads a date range. The NDJSON form is one entry per line in chain order and a trailer line:

{ "trailer": "cardoffice-audit-export", "version": 1, "organisationId": "7e0d…",
"from": "2026-10-01T00:00:00.000Z", "to": "2026-11-01T00:00:00.000Z", "entries": 3120,
"sha256": "…", "signature": "…", "keyId": "3f9a1c2b4d5e6f70" }

sha256 is over every byte above the trailer. signature is Ed25519 over the string cardoffice-audit-export|v1|<sha256>, base64, or null when CardOffice has no signing key configured. Verify with ops/verify-audit-export.mjs from the CardOffice repository, which checks the hash, the signature against the public key, and the chain links and numbering inside the file:

Terminal window
curl -s https://app.cardoffice.nz/api/audit/public-key | jq -r .data.publicKeyPem > cardoffice-audit.pem
node ops/verify-audit-export.mjs audit-2026-10-01_2026-10-31.ndjson cardoffice-audit.pem

The CSV form carries the same columns for a spreadsheet and is not signed.

Just after midnight UTC, CardOffice writes a digest per organisation for the previous day: the first and last seq, the count, and the chain’s head hash after the last entry, signed with the same key. Digests are listed on the settings page and are what lets a chain still verify after entries older than the organisation’s audit retention have been removed.

GET/api/audit/public-keyno authentication
{ "data": { "algorithm": "Ed25519", "keyId": "3f9a1c2b4d5e6f70", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\n…" } }

keyId is the first 16 hex characters of the SHA-256 of the PEM with its whitespace removed; digests and export trailers name the key they were signed with so a rotated key can still be matched.

Settings, Audit log, Verify the whole chain now recomputes every entry’s hash in the database and checks every link and number. Faults are reported by position, and the verification is itself logged.

© 2026 Secure ID Ltd. All rights reserved.Documentation licence and terms