Skip to content

Audit Logs

Whatomate keeps a record-level audit trail of every create / update / delete on the resources that matter for compliance and debugging. Each entry is scoped to an organization and answers the four standard questions: who did what to which record, and when.

Two surfaces, same data:

  • Settings → Audit Logs — the global activity feed across all resources, with filters (resource type, user, action, date range).
  • Per-resource activity tab — every detail view (Account, Team, Campaign, Template, etc.) embeds an activity panel showing only the entries for that one record. The Chatbot Settings page goes further: each tab (Messages, Agents, Hours, SLA, AI) has its own activity feed for just that section.

Audit logging is hooked into the handlers that mutate user-facing resources. The following resource types are recorded today:

Resource typeSource
accountWhatsApp account create / update / delete
campaignCampaign lifecycle
chatbotChatbot flow create / update / delete
contactContact create / update / delete
ivr_flowIVR flow definitions
organizationOrg settings changes
roleCustom role create / update / delete
teamTeam membership and config
templateTemplate create / publish / delete
userUser create / update / disable
webhookOutbound webhook config
ai_contextChatbot AI context entries
keyword_ruleChatbot keyword auto-reply rules
settings.chatbot.messagesChatbot greeting / fallback / out-of-hours messages
settings.chatbot.agentsChatbot routing config
settings.chatbot.hoursBusiness-hours config
settings.chatbot.slaSLA / escalation config
settings.chatbot.aiAI provider / model config
FieldDescription
idUUID of the audit entry.
organization_idAlways set; audit logs never leak across orgs.
resource_typeOne of the values above.
resource_idUUID of the affected record.
user_id, user_nameThe actor. user_name is captured at write time, so renames don’t rewrite history.
actionOne of created, updated, deleted.
changesJSON array of { field, old_value, new_value } triples — the diff.
created_atWhen the change happened.

For a created entry, every field has old_value: null. For deleted, every field has new_value: null. For updated, only the fields that actually changed are recorded — saves with no diff are silently dropped, so a busy team’s audit log doesn’t fill up with no-op edits.

{
"action": "updated",
"changes": [
{ "field": "name", "old_value": "Sales Team", "new_value": "Sales Team Asia" },
{ "field": "is_active", "old_value": true, "new_value": false }
]
}

A small set of fields is excluded from diffs to keep the audit log readable and to avoid logging anything sensitive or noisy:

  • Metadata: id, created_at, updated_at, deleted_at, organization_id, created_by, updated_by, created_by_id, updated_by_id.
  • Sensitive / opaque values: webhook_verify_token, header_media_id, header_media_local_path, meta_template_id, api_config.
  • Bulk / structural fields that would dwarf the diff: members, recipients, steps, buttons, sample_values, menu, panel_config, canvas_layout, completion_config, conditions, cancel_keywords, welcome_audio_url.

A few fields are flattened instead of dropped — for example, an updated keyword rule logs response_content as just its body sub-field rather than the entire JSON blob, so you see the human-readable change.

Reading audit logs requires the audit_logs:read permission. There’s no write, delete, or edit permission for this resource — audit logs are append-only. The system writes them; nobody (including super admins) can amend or delete them through the API.

By default, only the admin role has audit_logs:read. To let an agent role read audit logs, add the permission under Settings → Roles → <role name>.

audit.LogAudit writes the entry in a goroutine, off the request’s critical path. Two consequences worth knowing:

  1. A successful API mutation returns to the client before the audit row hits the DB. In e2e tests, helpers like verifyAuditLogged() poll for up to 3 seconds to handle this.
  2. If the audit insert itself fails (DB issue, validation error), the failure is logged via slog but does not propagate to the user — the original mutation still succeeded. Audit gaps are visible in the application log.

GET /api/audit-logs accepts these query parameters:

ParamNotes
resource_typeExact match against the strings in the table above.
resource_idUUID of a single record — use this to fetch the entire history of one row.
user_idUUID of the actor.
actioncreated / updated / deleted.
from, toYYYY-MM-DD (date) or RFC 3339 (2026-04-01T00:00:00Z) timestamps. from is inclusive at start of day; to is inclusive through end of day for date-only inputs.
page, limitStandard pagination. Results are ordered created_at DESC.

Examples:

Terminal window
# Everything one user did in the last week
curl --cookie "..." \
"$BASE/api/audit-logs?user_id=<uuid>&from=2026-04-26"
# Full history of a specific template
curl --cookie "..." \
"$BASE/api/audit-logs?resource_type=template&resource_id=<uuid>"
# All deletions in March
curl --cookie "..." \
"$BASE/api/audit-logs?action=deleted&from=2026-03-01&to=2026-03-31"

The full schema is documented under API Reference → Audit Logs(coming soon — page not yet authored).

There’s no automatic retention or rotation today. Entries accumulate forever. For long-running deployments you can prune via SQL on a schedule:

-- Drop audit entries older than 1 year
DELETE FROM audit_logs WHERE created_at < NOW() - INTERVAL '1 year';

Keep in mind: doing this loses the ability to answer “what was the state of X six months ago” for any field that has changed since.

A few classes of state change deliberately or incidentally don’t show up in audit logs:

  • Message sends — sending a WhatsApp message is a routine operation; it shows up in conversation history rather than the audit log.
  • Authentication events — logins, refreshes, SSO callbacks, API key uses. These are in the application log (slog), not the audit table.
  • Chat-side operations — assigning a conversation, transferring to an agent, marking resolved. Tracked in conversation state rather than the audit log.
  • Background job runs — campaign worker progress, SLA breach checks, webhook delivery. These are operational signals, logged elsewhere.

If you need any of the above for compliance, treat the application log + the conversation/transfer/job tables as additional sources alongside the audit log.