Docs

Everything you need to send your first event, list and fetch events, verify chain integrity, and pin the chain tip for external tamper evidence. Pick your language below and every example on the page follows it.

Install and authenticate

Install the package from npm:

Then construct a client with your API key. Create one from the dashboard — Settings → API Keys.

// Install once:
//   npm install evelmo

import { Evelmo } from "evelmo";

const evelmo = new Evelmo({
  apiKey: process.env.EVELMO_API_KEY!,
});

The API is reachable at https://api.evelmo.com. Every request authenticates via the x-api-key header.

Log an event

Every event needs an action (dot-namespaced), an actionType (one of C, R, U, D, A), a human-readable eventName, plus an actor and a target. groupId, context, metadata, and occurredAt are optional.

The call returns the saved log, including the sequence number (seq) and leafHash that the chain commits to. Hold onto those values if you want to pin a specific event for later independent verification.

await evelmo.log({
  action: "user.invited",
  actionType: "C",
  eventName: "User Invited",
  actor: { id: "usr_123", name: "Stefan" },
  target: { id: "usr_456", type: "user" },
  groupId: "tenant_abc",          // optional — your own tenant/org id
  metadata: { role: "admin" },    // optional — arbitrary JSON
});

List events

Paginated, filterable. Without parameters you get the most recent 50 events for your organization, newest first. action supports prefix wildcards — e.g., user.* matches user.invited, user.deleted, etc.

const { data, pagination } = await evelmo.list({
  page: 1,
  limit: 50,
  action: "user.*",
  actorId: "usr_123",
  from: new Date("2025-01-01"),
  to: new Date(),
});

// data: AuditLog[]
// pagination: { page, limit, total, totalPages }

Fetch a single event

Look up a single log by the _id that was returned when you created it.

const event = await evelmo.get("6620a1a4c8f19b000123");

Verify chain integrity

Re-hashes every event in sequence order and checks that the chain is intact. Returns valid: true plus the first and last hash on success, or valid: false with a brokenAt index if tampering is detected.

const result = await evelmo.verify();

if (!result.valid) {
  console.error("Chain broken at position", result.brokenAt);
}

// result.firstHash / result.lastHash are safe to pin externally.

Full verification cost is O(N) in the chain length. The dashboard's chain-health card uses an incremental, cached verifier — you don't need to call this from UI code, only from audit scripts or on-demand checks.

Pin the chain tip

The cheapest tamper-evidence check: fetch the current tip of the chain — its latest sequence number and hash — and persist it somewhere outside Evelmo (your own database, a weekly email to auditors, an S3 object with Object Lock). If history is later altered, the stored tip will no longer match the live one.

const tip = await evelmo.getTip();
// {
//   seq: 42,
//   leafHash: "a4c8e3f19b...",
//   occurredAt: "2026-04-18T12:00:00.000Z",
//   chainLength: 42,
// }

What happens when Evelmo is down

evelmo.log() is **non-blocking by default**. The call enqueues the event in-process and returns in microseconds; a background worker delivers it with exponential-backoff retry and an auto-generated idempotency key. **Your request path never waits on Evelmo's network**, and a short Evelmo outage is absorbed by the queue rather than surfaced to your users.

During an outage, events buffer locally up to maxQueueSize (default 10,000). If the queue fills, the oldest events are dropped first and onDrop fires. If an individual event exhausts maxRetries (default 5, backoff capped at 30s), onError fires. Idempotency keys mean the same event never lands twice, even across retries or cold-starts within the key's retention window.

Call flush() on shutdown so buffered events drain before the process exits. Inspect getStats() for pending / sent / dropped / failed counts.

// Default async. log() returns as soon as the event is buffered.
evelmo.log({
  action: "user.signed_in",
  actionType: "A",
  eventName: "Signed in",
  actor:  { id: "usr_123" },
  target: { id: "usr_123", type: "user" },
});

// Drain on shutdown.
process.on("SIGTERM", async () => {
  await evelmo.flush(5_000);
  process.exit(0);
});

Need back-pressure instead? Pass { sync: true } to await the network call and get the saved AuditLog back (throws on failure). Use this for writes where losing the event is worse than blocking — typically financial, regulatory, or compliance-critical trails.

Idempotent retries

Network hiccups shouldn't duplicate audit events. Pass an Idempotency-Key with your request and Evelmo will return the original log on any retry with the same key, scoped per organization. A safe default is the id of the underlying business event (transaction id, webhook id, request id).

await evelmo.log(input, {
  idempotencyKey: `txn-${transactionId}`,
});

Keys are ≤128 characters. Same key, same result — different bodies with the same key do not create new logs; the server returns the original.