Skip to main content
Your progress
0 of 6 complete0%
T4 / M4.3 / L2 OF 5 / Architect LEVEL / 9 min

Request and response body capture

Outcome

By the end of this lesson, you will be able to read a captured request/response body in the audit log, predict which bodies are stored, dropped or truncated, and decide what to do when your data policy forbids storing payloads.


PrerequisitesM4.3.L1: What gets logged
Time9 minutes

1. Concept

Recording that something happened is not the same as being able to investigate it.

For every change, ZopNight stores what was asked for and what came back (up to a size cap), alongside the time, the person, the path and the result.

The difference is this. “Somebody created a schedule on 20 May” tells you almost nothing. “Somebody created a schedule called business-hours-eu, with these crons, on these resources” tells you everything you would need to reconstruct it.

Terminal window
WHAT'S CAPTURED EXAMPLE
──────────────────────────────────────────────────────────
Request body The JSON payload posted to the API
Response body The JSON response returned

No headers (so no IP address or user agent) and no query string are stored. Body capture is always on: there is no org-level switch to turn it off, because the forensic value usually exceeds the storage cost.

Sensitive-data redaction

Credentials are kept out of the log in two ways, depending on how the change arrived. Both happen before storage and are irreversible: the value never reaches the log.

Terminal window
HOW IT ARRIVED WHAT HAPPENS
──────────────────────────────────────────────────────────────────
App or API For the areas that carry secrets, the request
AND response bodies are dropped whole:
cloud accounts, connect flows, integrations,
AI (keys, providers), app secrets, basic auth,
notification channels
The row still records who, method, path, status
and time. Every other body is stored as sent.
MCP tool call The arguments a tool declares as secret are
replaced with [REDACTED]; the rest of the
request is stored.

[REDACTED] therefore only ever appears on MCP rows. There is no masking of emails, account numbers, project IDs or subscription IDs, and no customer-defined redaction policy: the lists are fixed by ZopNight. Anything you send in an ordinary body (a schedule description, a dashboard name) is stored as sent. Notification channel changes are the exception: their bodies are dropped whole, so a webhook URL or Slack token never reaches the log.

Why redaction matters

Three reasons converge:

Terminal window
SECURITY Credentials never persist in the audit log.
If the audit log itself is ever exposed (export to
a misconfigured SIEM, for example), there are no
long-lived credentials to leak.
COMPLIANCE GDPR, CCPA, and similar regulations require data
minimization. Storing credentials in audit logs
without justification is a compliance finding.
Dropping credential bodies is the practical answer.
INCIDENT If the audit log were itself the source of a breach,
RESPONSE dropping secrets limits the blast radius. Defense
in depth: do not turn one log into a credential dump.

The trade-off: some debugging info is lost. For a cloud account, the log tells you who sent a PUT to that account’s path, when, and whether it succeeded, but not what was in the body. The current values are in the cloud-account record; the audit captures the event, not the secret. This is the right balance.

Body size limits

Terminal window
SIZE CAP 10,000 bytes by default, one setting for both
the request body and the response body
MAXIMUM 65,023 bytes (the limit of the storage column)
WHO SETS IT ZopNight, for the whole deployment (not per org)

A body larger than the cap is stored as its first part, cut cleanly at a character boundary, plus two extra fields: the original size in bytes and a SHA-256 hash of the full original body. There is no marker text inside the body; the presence of those two fields is what tells you it was cut. The hash lets you prove what the full body was if you hold a copy of it. This handles the rare very large payload without letting one outlier inflate audit storage.

In practice most mutations are small, so truncation is rare.

Reading a captured body

The Audit Logs page shows captured bodies inline. Click any audit entry and the request and response bodies render as JSON. The API returns the same data in the body and response_body fields.

Terminal window
AUDIT LOG ENTRY (POST /schedules)
─────────────────────────────────────────────────────────
Method: POST
Path: /schedules
Status: 201
REQUEST BODY:
{
"name": "business-hours-staging",
"timezone": "America/New_York",
"crons": [
{"cronExpression": "0 8 * * 1-5", "action": "start"},
{"cronExpression": "0 20 * * 1-5", "action": "stop"}
]
}
RESPONSE BODY:
{
"id": "sch_xyz789",
"name": "business-hours-staging",
"created_at": "2026-05-20T16:42:11Z",
"resource_count": 7
}

The response body’s id is the handle for tracing follow-up events on the same schedule. Put sch_xyz789 in the resource (path) filter to find every subsequent mutation against this schedule.

Forensic use cases

The patterns that recur:

Terminal window
QUESTION AUDIT-LOG QUERY
──────────────────────────────────────────────────────────────────
"Who last changed this schedule?" resource = schedules/<id>
method = PUT
→ most recent entry, with user
"What was the schedule like before?" Same query as above; the
captured request body is the
new value; previous values are
in earlier entries (compare)
"Why did the schedule change times?" resource contains the schedule id
look for a PUT changing crons
"Who deleted resource X?" method = DELETE; path matches
→ user + timestamp
"What did this failed API call look like?" status = failed; user = …
→ error context
"Reproduce a customer's issue" user = customer
recent actions in time window

The combination of mutations-only logging + body capture is what makes these queries productive. Without body capture, you would know an event happened but not its details. With it, the audit log doubles as a state-change history.

If your policy forbids payload storage

There is no switch to turn body capture off and no retention cutoff: rows are kept indefinitely. If your data policy forbids storing request payloads, the practical options are to keep sensitive values out of ordinary request bodies and to raise the requirement with ZopNight before you adopt the product. Most orgs are well served by the default, because the storage cost is modest relative to the forensic value.

How ZopNight uses body capture

Body capture lives in one place: the gateway, which every request passes through. For app and API requests it keeps the raw request and response bodies as they are (or drops them for the credential areas), applies the size cap, and writes the entry to the org’s Redis Stream. Only MCP calls are parsed, so that declared secret arguments can be replaced with [REDACTED]. Because it sits in the gateway, every mutation is captured the same way with no per-service code.


2. Demo

A real-feeling forensic investigation:

Terminal window
USER reports: "Since May 15 my staging resources stay up until
after midnight, every day."
INVESTIGATION via Audit Logs:
T+0 Open Activity → Audit Logs
Filter: resource contains "sch_xyz789", method = PUT
T+30 sec Found entry on 2026-05-15T14:23:17Z:
User: peter@example.com
Method: PUT
Path: /schedules/sch_xyz789
Status: 200
T+45 sec Click the entry. REQUEST BODY:
{
"crons": [
{"cronExpression": "0 8 * * 1-5", "action": "start"},
{"cronExpression": "30 0 * * *", "action": "stop"} ← here
]
}
T+1 min Compare to the previous entry (creation):
second cron was "0 20 * * 1-5" (Mon-Fri 20:00)
now it's "30 0 * * *" (every day at 00:30)
Peter accidentally edited the cron and broke the schedule.
The captured body confirms exactly what changed.
T+2 min Revert via UI; message Peter; document the lesson.
ELAPSED: ~10 minutes from question to root cause + fix.

Without body capture, the audit log would say “Peter sent a PUT to sch_xyz789 on May 15.” The investigation would require finding Peter, asking what he changed, hoping he remembers. With body capture, the change is in the log.


3. Hands-on (5 min)

Pick one mutation from your audit log and read it end-to-end:

Terminal window
PICK an audit entry from the last 30 days. Capture:
Timestamp: __________
User: __________
Method+path: __________
Status: __________
REQUEST BODY (paste a small portion):
__________________________________________________________
__________________________________________________________
RESPONSE BODY (paste a small portion):
__________________________________________________________
BODY DROPPED or [REDACTED] (if any; which area or tool?):
__________
This is the exact form an auditor or incident responder would see.

If your team has been using body-capture mode without realizing it, this exercise often surprises people with how much detail is preserved.


4. Knowledge check

Q1

A request body contains a cloud credential. The audit log stores:

A. The full credential, since it would be needed for forensics later on
B. The credential, but encrypted at rest with the organisation’s own key
C. [REDACTED] in place of the credential field; other fields stored unchanged
D. No request or response body at all; just who, method, path, status and time

Show answer

Correct: D. For the credential areas (cloud accounts among them), both bodies are dropped whole before storage. Field-level [REDACTED] (option C) happens only on MCP tool calls. The credential itself lives in the encrypted cloud-account record, never in the audit log.

Q2

A request body of 200 KB is sent. What lands in the audit log?

A. The first 10,000 bytes, plus the original size and a hash of the full body
B. The full 200 KB body, stored exactly as it was sent to the API endpoint
C. Nothing; the request is rejected at the gateway for being too large
D. A random sample of the body, taken at ingest time by the middleware

Show answer

Correct: A. With the default cap, the first 10,000 bytes are kept (more if ZopNight has raised the cap, never above 65,023), and the row records the original size and a SHA-256 of the full body. Those two fields, not a marker in the text, are how an investigator knows the body was cut.

Q3

Body capture applies to GET requests:

A. Yes, with precisely the same redaction pass applied to them
B. No: body capture only applies to mutations (POST/PUT/PATCH/DELETE)
C. Only on those GET requests that happen to carry a request body
D. Only on the GET requests that ultimately returned successfully

Show answer

Correct: B. App and API GETs are not logged at all, refused ones included, so there is nothing to capture. The only GET-labelled rows are MCP read-tool calls, which store the request but not the response.


5. Apply

Click any entry in Activity → Audit Logs to read the captured bodies. The API (GET /orgs/{orgID}/audit-logs) returns the same rows, including the body and response_body fields.

For forensic playbooks, write down a few common filters in your runbook: “who changed this schedule” (resource contains the id, method PUT), “all DELETEs last 7 days” (method DELETE, date range), “all failed mutations” (status = failed). These three cover most ad-hoc investigations.


Glossary terms touched

Redaction


Start with the bill.

Foundations takes about five hours. The first lesson is nine minutes.

Free curriculum. No paywall. 290 lessons across 7 courses, plus an optional 24-lesson beginner course. Browse every course without an account; a free account opens the lessons, saves your progress and opens the exams for three publicly verifiable credentials. Read it on the train, take the exam on a Saturday, list the credential on your résumé Monday.

5h median time to finish Foundations
free account, every course, no paywall
3 certifications, each with a public verifier
Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console· Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console·