Audit streams
Push a customer's signed audit events to their SIEM or webhook as they happen.
/v1/audit/orgs/{id}/streamsCreate a webhook stream
Registers an https destination that receives the org's new events as signed JSON batches, and returns the stream with its signing secret shown once.
Content-TypeMust be application/json.
idThe aorg_ id or your organization_id; both resolve to the same org, scoped to your tenant.
typeDestination type; only webhook is accepted today. One of webhook.
urlAbsolute https URL that will receive POST deliveries; the host must resolve to a public address.
- Deliveries are POST requests with Content-Type application/json, User-Agent Invoance-Audit/1 and X-Invoance-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 with the signing secret over "<t>.<body>".
- The body is {"events": [...], "count": n, "delivered_at": "<RFC 3339>"}; events are full event objects in ascending seq order, up to 100 per delivery.
- Only a 2xx from your endpoint counts as delivered; 408, 425, 429, any 5xx, timeouts and connection errors put the stream in state error and retry after 2^failure_streak seconds up to 300; any other 4xx puts it in state invalid and stops it until you delete and recreate the stream.
- The destination is checked for private, loopback, link-local and metadata addresses both at creation and again at every delivery, and redirects are never followed.
- The stream starts at the org's current seq; events already in the log are not replayed, and events that reached cold storage before delivery are skipped with a warning in the server log.
- The SDKs default type to webhook when omitted.
import { InvoanceClient } from "invoance";
// Reads INVOANCE_API_KEY from the environment.
const client = new InvoanceClient();
const stream = await client.audit.streams.create("org_8472", {
url: "https://siem.example.com/hooks/invoance",
});
// Store signing_secret now; it is not returned again.
console.log(stream.id, stream.signing_secret);
{
"id": "astr_01J0Y3N5P7R9T1V3X5Z7B9D1FG",
"type": "webhook",
"endpoint": "https://siem.example.com/hooks/invoance",
"state": "active",
"cursor_seq": 41,
"signing_secret": "whsec_4c1d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d"
}
idStream id, astr_ followed by a ULID.
typeDestination type; only webhook can be created. One of webhook.
endpointThe https URL deliveries are posted to, as validated at create time.
stateactive while deliveries succeed, error while a transient failure is being retried with backoff, invalid once a permanent failure stopped the stream. One of active, error, invalid.
cursor_seqThe org's last_seq at creation; delivery starts with the next event, nothing older is replayed.
signing_secretwhsec_ followed by 64 hex characters; the HMAC key for X-Invoance-Signature, returned only in this response.
unsupported_stream_typetype is not webhook.
invalid_urlurl is not an absolute URL.
not_httpsurl does not use https.
no_hosturl has no host.
resolution_failedThe host did not resolve in DNS.
forbidden_destinationThe host resolves to a private, loopback, link-local or cloud metadata address.
too_many_streamsThe org already has as many streams as the plan allows.
insufficient_scopeThe key does not have audit:write; audit:read alone is not enough.
not_foundNo audit org with that id or organization_id belongs to the tenant.
org_archivedThe org is archived; unarchive it first.
rate_limitedThe tenant used up its per-second or per-minute request budget; the Retry-After header says when to retry.
db_errorA database query failed.
missing_api_keyNeither an Authorization header nor an X-API-Key header was sent.
invalid_authorization_schemeAn Authorization header was sent without the Bearer scheme.
invalid_api_key_formatThe key does not start with invoance_live_.
invalid_api_keyThe key does not match any API key.
api_key_revokedThe key has been revoked.
ip_not_allowedThe key has an IP allowlist and the caller's address is not on it.
api_key_lookup_failedThe key could not be looked up in the database.
/v1/audit/orgs/{id}/streamsList an org's streams
Returns up to 100 streams of the org with their delivery state, newest first, never including secrets.
idThe aorg_ id or your organization_id; both resolve to the same org, scoped to your tenant.
- A stream in state invalid stays listed with its last_error until you delete it.
import { InvoanceClient } from "invoance";
// Reads INVOANCE_API_KEY from the environment.
const client = new InvoanceClient();
const result = await client.audit.streams.list("org_8472");
for (const stream of result.streams as Array<Record<string, unknown>>) {
console.log(stream.id, stream.state, stream.cursor_seq, stream.last_error);
}
{
"streams": [
{
"id": "astr_01J0Y3N5P7R9T1V3X5Z7B9D1FG",
"type": "webhook",
"endpoint": "https://siem.example.com/hooks/invoance",
"state": "active",
"cursor_seq": 42,
"failure_streak": 0,
"last_error": null,
"last_delivery_at": "2026-09-22T08:14:09.104233+00:00",
"created_at": "2026-09-22T08:10:00.418212+00:00"
}
]
}
streamsThe streams, ordered by created_at descending.
streams[].idStream id, astr_ followed by a ULID.
streams[].typeDestination type; only webhook can be created. One of webhook.
streams[].endpointThe https URL deliveries are posted to, as validated at create time.
streams[].stateactive while deliveries succeed, error while a transient failure is being retried with backoff, invalid once a permanent failure stopped the stream. One of active, error, invalid.
streams[].cursor_seqThe highest seq delivered so far; the next delivery starts at cursor_seq + 1.
streams[].failure_streakConsecutive failed deliveries; reset to 0 on success and used to compute the backoff.
streams[].last_errorMessage from the last failed delivery, or null.
streams[].last_delivery_atWhen the last successful delivery was recorded, or null.
streams[].created_atWhen the stream was created.
insufficient_scopeThe key has neither audit:read nor audit:write; a ledger key with only read or write is rejected on audit routes.
not_foundNo audit org with that id or organization_id belongs to the tenant.
rate_limitedThe tenant used up its per-second or per-minute request budget; the Retry-After header says when to retry.
db_errorA database query failed.
missing_api_keyNeither an Authorization header nor an X-API-Key header was sent.
invalid_authorization_schemeAn Authorization header was sent without the Bearer scheme.
invalid_api_key_formatThe key does not start with invoance_live_.
invalid_api_keyThe key does not match any API key.
api_key_revokedThe key has been revoked.
ip_not_allowedThe key has an IP allowlist and the caller's address is not on it.
api_key_lookup_failedThe key could not be looked up in the database.
/v1/audit/orgs/{id}/streams/{stream_id}Delete a stream
Removes the stream so no further deliveries are made, and returns the deleted id.
idThe aorg_ id or your organization_id; both resolve to the same org, scoped to your tenant.
stream_idThe astr_ id of the stream.
- Deleting works on archived orgs; only creating and testing streams is blocked by archiving.
import { InvoanceClient } from "invoance";
// Reads INVOANCE_API_KEY from the environment.
const client = new InvoanceClient();
const result = await client.audit.streams.delete("org_8472", "astr_01J0Y3N5P7R9T1V3X5Z7B9D1FG");
console.log(result.deleted, result.id);
{
"deleted": true,
"id": "astr_01J0Y3N5P7R9T1V3X5Z7B9D1FG"
}
deletedAlways true on success.
idThe astr_ id that was deleted.
insufficient_scopeThe key does not have audit:write; audit:read alone is not enough.
not_foundNo org with that id belongs to the tenant, or no stream with that id belongs to the org.
rate_limitedThe tenant used up its per-second or per-minute request budget; the Retry-After header says when to retry.
db_errorA database query failed.
missing_api_keyNeither an Authorization header nor an X-API-Key header was sent.
invalid_authorization_schemeAn Authorization header was sent without the Bearer scheme.
invalid_api_key_formatThe key does not start with invoance_live_.
invalid_api_keyThe key does not match any API key.
api_key_revokedThe key has been revoked.
ip_not_allowedThe key has an IP allowlist and the caller's address is not on it.
api_key_lookup_failedThe key could not be looked up in the database.
/v1/audit/orgs/{id}/streams/{stream_id}/testSend a test delivery
Posts one synthetic stream.test event to the stream's destination with a real signature and returns what the destination answered.
idThe aorg_ id or your organization_id; both resolve to the same org, scoped to your tenant.
stream_idThe astr_ id of the stream.
- The test event is {id: aevt_..., action: "stream.test", actor: {type: "system", id: "invoance"}, targets: [], occurred_at, metadata: {test: true}}; it is not stored, not signed with the tenant key and carries no seq, payload_hash or signature.
- The delivery is wrapped and signed exactly like a real batch, so it exercises your X-Invoance-Signature check.
- A test never moves cursor_seq or changes the stream's state; the response waits for the destination up to 15 seconds.
import { InvoanceClient } from "invoance";
// Reads INVOANCE_API_KEY from the environment.
const client = new InvoanceClient();
const result = await client.audit.streams.test("org_8472", "astr_01J0Y3N5P7R9T1V3X5Z7B9D1FG");
console.log(result.delivered, result.http_status, result.error);
{
"delivered": true,
"http_status": 200,
"error": null,
"retryable": false
}
deliveredTrue when the destination answered with a 2xx.
http_statusThe destination's status code, or null when no response arrived.
errorWhy the delivery failed, or null on success.
retryableTrue when the failure is one the dispatcher would retry (408, 425, 429, 5xx, network); false on success or a permanent failure.
unsupported_stream_typeThe stream is not a webhook stream.
no_endpointThe stream has no endpoint stored.
insufficient_scopeThe key does not have audit:write; audit:read alone is not enough.
not_foundNo org with that id belongs to the tenant, or no stream with that id belongs to the org.
org_archivedThe org is archived; unarchive it first.
rate_limitedThe tenant used up its per-second or per-minute request budget; the Retry-After header says when to retry.
db_errorA database query failed or the stream's stored secret could not be decrypted.
missing_api_keyNeither an Authorization header nor an X-API-Key header was sent.
invalid_authorization_schemeAn Authorization header was sent without the Bearer scheme.
invalid_api_key_formatThe key does not start with invoance_live_.
invalid_api_keyThe key does not match any API key.
api_key_revokedThe key has been revoked.
ip_not_allowedThe key has an IP allowlist and the caller's address is not on it.
api_key_lookup_failedThe key could not be looked up in the database.
/v1/audit/eventsIngest an audit eventGET/v1/audit/eventsList audit eventsGET/v1/audit/events/{id}Get an audit eventGET/v1/audit/events/{id}/verifyVerify an audit event/v1/audit/orgsCreate an audit orgGET/v1/audit/orgsList audit orgsPATCH/v1/audit/orgs/{id}Rename an audit orgDELETE/v1/audit/orgs/{id}Delete an audit orgPOST/v1/audit/orgs/{id}/archiveArchive an audit orgPOST/v1/audit/orgs/{id}/unarchiveUnarchive an audit orgGET/v1/audit/orgs/{id}/integrityCheck an org's sequence integrityPUT/v1/audit/orgs/{id}/retentionSet an org's retention/v1/audit/portal/exchangeExchange a portal link tokenGET/v1/audit/portal/eventsList events through the portalGET/v1/audit/portal/events/{id}Get an event through the portalGET/v1/audit/portal/events/{id}/verifyVerify an event through the portalGET/v1/audit/portal/orgGet the portal's org and issuerGET/v1/audit/portal/streamsList streams through the portalPOST/v1/audit/portal/streamsCreate a stream through the portalDELETE/v1/audit/portal/streams/{id}Delete a stream through the portalPOST/v1/audit/portal/streams/{id}/testTest a stream through the portal