# Integration Forensics

> Debug a failing integration with a third-party API, webhook, or vendor system, where the other side does not behave the way its documentation says. Establishes what actually crossed the wire before theorising, and works through the specific ways external systems lie. Use when a webhook is firing wrong, a vendor API returns something unexpected, data is missing or duplicated after a sync, or the user says "their API is broken" or "this worked yesterday".

- Skill: `raz-gits/integration-forensics` (Agent Skill)
- Install (CLI): `npx skillmds@latest add raz-gits/integration-forensics`
- Raw SKILL.md: https://api.skillmd.com/api/skills/raz-gits/integration-forensics/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: Raz-Gits (https://skillmd.com/u/raz-gits)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/raz-gits/integration-forensics

---


# integration forensics

When your code talks to a system you do not control, the bug is usually not in
your code. It is a **false belief about what the other side does**, and no amount
of reading your own code will surface it.

So the method inverts. Do not reason about the code. Establish what actually
crossed the wire, then reason about the difference between that and what you
expected.

> **The documentation describes intended behaviour. You need observed behaviour,
> and they are different systems.**

## Step 1, get the raw payload, before anything touches it

Not the parsed object. Not the logged summary. The bytes.

Most integration bugs are invisible after parsing, because the parse is where
the wrong assumption gets applied. A field arrives as a string instead of a
number, an array has one element instead of five, a key is absent rather than
null, and the parsed object looks perfectly reasonable.

If there is no raw log, adding one is the first fix, not a detour. Log the
untouched body and the headers, on every inbound call, before validation. Keep a
rolling window. This one change resolves more integration bugs than any amount
of instrumentation downstream, and it costs almost nothing.

Then **reproduce from a stored payload, never from live traffic.** Live traffic
is not reproducible, arrives when it wants, and cannot be replayed against a fix.
A saved payload is a test case.

## Step 2, the catalogue

Nearly every third-party integration bug is one of these. Work down the list
against the raw payload rather than guessing.

**It returns success for something that is gone.** A 200 with a body, for a
resource that no longer exists. Job boards do this constantly: the posting page
serves fine while the job is absent from the board's own listing API. Never treat
a status code as proof a thing exists. Check it against the collection it should
be in.

**One logical action fires many events.** A phone call is not one webhook. It is
Setup, Proceeding, Answered, Disconnected, each a separate delivery. If your
handler assumes one event per call, everything downstream double-counts. Find out
how many events the vendor emits per real-world action before writing the handler.

**Events go missing.** The provider drops one, usually the terminal one, and now
you have a record that never closes. Assume the closing event may never arrive
and build the sweep that closes stale records on its own. This is not a rare
edge; it is a weekly occurrence at volume.

**Events arrive out of order.** The completion lands before the creation.
Ordering is not guaranteed unless the vendor explicitly guarantees it, and mostly
they do not. Handle by state rather than by sequence.

**Retries look like new events.** The vendor did not get your 200 fast enough and
sent it again. Without an idempotency key on the event id, you process it twice.
Check whether their retry carries the same id or a new one, because both designs
exist.

**Documented fields are absent, or a different type.** Especially on older
records, free accounts, and objects created before their last migration. Never
let a missing optional field crash the whole batch.

**Success that silently truncates.** Rate limits and pagination that return 200
with fewer results rather than an error. If a count looks round, or exactly at a
limit, suspect this before suspecting your filter.

**A field that contradicts the record.** The metadata says one thing and the
actual object says another. A listing marked remote whose own application form
requires five days on site. When a summary field and the detail disagree, **the
detail is the truth and the summary is marketing.**

## Step 3, bisect the boundary

Once you have the raw payload, the question is only ever which side is wrong, and
there are three places to look:

1. **What they sent.** Compare the raw body to their docs. A mismatch here is
   their bug, or your version is out of date, and either way the fix is not in
   your parser.
2. **What you understood.** Parse the raw body in isolation. If the parse is
   wrong, you have found it, and now you have a regression test for free.
3. **What you did with it.** Only reach here after the first two are cleared.

Doing these out of order is what turns an afternoon into a week.

## Step 4, build the thing that would have told you

An integration that broke silently will break silently again. Before closing it
out, add whichever of these is missing:

- **A heartbeat.** Write a timestamp on every successful run and alert when it
  goes stale. This catches the worst failure mode, which is a job that exits zero
  and writes nothing. A non-zero exit gets noticed; silence does not.
- **A floor.** If the normal result is thousands of rows, zero is a failure, not
  a quiet day. Assert it.
- **A comparison to yesterday.** A collapse from four thousand to six is not a
  crash and no exception will fire.
- **The raw log, if you had to add it in step 1.** Keep it.

## When their system and their docs disagree

The observed behaviour is the specification. Build for what you measured, leave a
comment saying it contradicts the documentation and on what date you measured it,
and move on. Waiting for a vendor to fix their docs is not a plan.

Report it to them anyway. It occasionally works, and it costs one email.

## Rules

- **Never theorise before reading the payload.** The whole failure mode of
  integration debugging is confident reasoning about imagined data.
- **One change at a time.** Fixing three suspected causes at once means never
  learning which one it was, and integration bugs recur.
- **Their status code is not evidence.** Neither is their summary field.
- **Write the regression test from the real payload**, exactly as received,
  including the parts you think are irrelevant.
- **Say "I don't know what they send" out loud** rather than assuming. It is the
  sentence that leads to looking, and looking is the entire method.

