Skip to content

Capture your first session

By the end of this guide you will have a real *.case.json file, committed to your repo, that you can replay from any SDK. The capture step is language-independent — it's the same curl calls (or CLI commands) no matter what your app or your tests are written in.

Time: 10 minutes. You need: a running Softprobe stack (runtime + proxy). If you don't have one yet, follow Installation first.

The plan

text
1. Start a capture session           ──► sessionId
2. Drive traffic through the proxy,     └─ every request carries
   header: x-softprobe-session-id         x-softprobe-session-id: $sessionId
3. Close the session                  ──► runtime flushes traces to disk
4. Inspect and commit the case file

1. Start a capture session

bash
softprobe session start --mode capture --json
# {"sessionId":"sess_01H7P8Q4XYZ...","sessionRevision":1}

# Or in a one-liner that exports to the shell:
eval "$(softprobe session start --mode capture --shell)"
echo "Session: $SOFTPROBE_SESSION_ID"

--shell emits export SOFTPROBE_SESSION_ID=… so you can use it in subsequent commands without parsing JSON.

Using raw curl

bash
SESSION_ID=$(curl -s -X POST http://127.0.0.1:8080/v1/sessions \
  -H 'Content-Type: application/json' \
  -d '{"mode":"capture"}' | jq -r .sessionId)

echo "Session: $SESSION_ID"

Either way, you now hold a sessionId string.

2. Drive traffic through the proxy

Two rules:

  1. Every request must carry x-softprobe-session-id: $SESSION_ID.
  2. Every request must route through the ingress proxy — not direct to the app.

Manual testing with curl

bash
curl -v -H "x-softprobe-session-id: $SESSION_ID" \
  http://127.0.0.1:8082/checkout \
  -d '{"amount": 1000, "currency": "USD"}' \
  -H 'content-type: application/json'

Port 8082 in the reference stack is the ingress listener. The app lives on 8081 directly, but you should not hit it there during capture — the proxy is what observes the hop.

Driving from a realistic scenario

In real usage you run your app's own integration tests or a staging smoke test:

bash
# Example: drive a test harness that hits the SUT on :8082 with the session header
SOFTPROBE_SESSION_ID=$SESSION_ID npm run smoke-test

# Or run a browser-based flow
SOFTPROBE_SESSION_ID=$SESSION_ID playwright test --grep "checkout"

The constraint is mechanical: whatever is making the HTTP calls must propagate the session header. Most HTTP clients accept a per-request header map or a default header config.

Checking traffic is actually arriving

In a second terminal, watch the runtime logs:

bash
docker logs -f e2e-softprobe-runtime-1
# INFO handling POST /v1/inject sessionId=sess_… path=/checkout
# INFO handling POST /v1/traces spans=2 sessionId=sess_…

Or query the runtime for span counts:

bash
curl -s http://127.0.0.1:8080/v1/sessions/$SESSION_ID/stats | jq
# { "extractedSpans": 2, "injectedSpans": 2 }

If extractedSpans stays at 0 after you send traffic, your session header isn't reaching the proxy. See Troubleshooting.

3. Close the session

Closing flushes the buffered traces to disk and deletes the session from the runtime.

bash
softprobe session close --session $SESSION_ID
# {"closed": true, "casePath": "e2e/captured.case.json"}

# Or raw curl:
curl -s -X POST http://127.0.0.1:8080/v1/sessions/$SESSION_ID/close

By default the runtime writes to SOFTPROBE_CAPTURE_CASE_PATH. In the Docker Compose reference stack this is e2e/captured.case.json. Override it when you start the runtime:

bash
docker run \
  -e SOFTPROBE_CAPTURE_CASE_PATH=/cases/checkout.case.json \
  -v $PWD/cases:/cases \
  ghcr.io/softprobe/softprobe-runtime:v0.5

Or per-session (v0.6+):

bash
softprobe session close --session $SESSION_ID --out cases/checkout.case.json

4. Inspect the capture

Open it. It should be readable JSON:

bash
jq '.caseId, (.traces | length), (.traces[0].resourceSpans[0].scopeSpans[0].spans | length)' \
   e2e/captured.case.json
# "session-auto-20260420-103022"
# 2
# 1
bash
softprobe inspect case e2e/captured.case.json
# Case: session-auto-20260420-103022
# Traces: 2
# ┌────────────────┬────────┬──────────┬───────────────────────────┐
# │ Direction      │ Method │ Status   │ URL                       │
# ├────────────────┼────────┼──────────┼───────────────────────────┤
# │ inbound  (app) │ POST   │ 200      │ /checkout                 │
# │ outbound       │ POST   │ 200      │ https://api.stripe.com/…  │
# │ outbound       │ GET    │ 200      │ http://fragment/shipping  │
# └────────────────┴────────┴──────────┴───────────────────────────┘

This command is useful in CI: feed it a case file and it prints a diff-friendly summary.

Things to look for

CheckWhy
At least one inbound and one outbound spanOtherwise the capture only saw one leg — your app may not be routing egress through the proxy.
No leaked secretsScan for authorization, credit cards, emails. If present, add a redaction rule next capture or scrub before committing.
Status codes are what you expectA 500 captured here will replay as a 500.
traceId is 32 hex chars per spanShorter values break OTLP consumers.

5. Rename and commit

bash
mkdir -p cases
mv e2e/captured.case.json cases/checkout-happy-path.case.json
git add cases/checkout-happy-path.case.json
git commit -m "capture: checkout happy path baseline"

Naming convention

Name by business scenario: checkout-happy-path, checkout-declined-card, signup-with-oauth. Avoid test-oriented names (test_42.case.json) — two tests should be able to share the same case if they exercise the same scenario.

One-shot capture with the CLI

Steps 1–3 collapse into a single command for scripted use:

bash
softprobe capture run \
  --driver "npm run smoke-test" \
  --target http://127.0.0.1:8082 \
  --out cases/checkout-happy-path.case.json

capture run starts a session, sets SOFTPROBE_SESSION_ID for the driver process, runs it, closes the session, and writes the case file. Useful in CI capture jobs.

Redacting sensitive data before writing

Capture mode respects capture_only rules with a redact payload. Apply them before you start driving traffic:

bash
softprobe session start --mode capture --json > session.json
SESSION_ID=$(jq -r .sessionId session.json)

softprobe session rules apply --session $SESSION_ID \
  --file rules/redact.yaml
yaml
# rules/redact.yaml
version: 1
rules:
  - id: strip-auth
    priority: 100
    when: { direction: outbound }
    then:
      action: capture_only
      captureOnly:
        redactHeaders: [authorization, cookie, x-api-key]
        redactJsonPaths:
          - "$.card.number"
          - "$.user.ssn"

Bodies and headers listed here will be replaced with "[REDACTED]" in the captured file.

Capturing from a production canary

Capture is safe for a small fraction of production traffic: the WASM filter mirrors observed bytes asynchronously — the request path itself is unaffected. To capture from production:

  1. Deploy your sidecar with sp_backend_url pointed at a dedicated capture-only runtime (not the test runtime).
  2. On the caller side, set x-softprobe-session-id only for the 0.1% of requests you want to sample.
  3. Stream captures to object storage via the runtime's SOFTPROBE_CAPTURE_CASE_PATH=s3://… (hosted feature).
  4. Download, redact, review, commit.

See Kubernetes deployment for the manifests.

Next

I want to…Read
Turn this case into a passing testReplay in Jest / pytest / JUnit / Go
Rewrite a captured response before replayWrite a hook
Scale to hundreds of casesRun a suite at scale

Released under the Apache-2.0 license.