Designing a data flow you can prove: building an integration audit trail that holds up
Most integrations are designed to move data correctly. Far fewer are designed so that, nine months later, somebody can demonstrate exactly what moved, when, and why it produced the figure now sitting in the ledger.
- What an integration audit trail actually has to answer
- The record structure: correlation, identity and intent
- Write before you act, and never update in place
- Retention, redaction and the awkward middle
- Making the integration audit trail usable by the people who need it
- Related platform guides
- Key points
What an integration audit trail actually has to answer
An integration audit trail is not a log file. Logs are written for engineers debugging a failure in the hour after it happens, and they are usually rotated away within a fortnight. An audit trail is written for a finance manager, an external auditor or a customer services supervisor who arrives with a specific question long after the event, and who has no access to your infrastructure and no interest in stack traces. The two artefacts have different readers, different retention periods and different design constraints, and treating one as a substitute for the other is the most common reason a project that worked fine technically becomes an embarrassment at year end.
In practice the questions asked after the fact are narrow and repetitive. Where did this invoice line come from, and what did the originating order look like at the moment we read it. Why did this order post to that nominal code when a near-identical order posted elsewhere. Did we send this despatch confirmation twice, or did the courier duplicate it. Why is the stock figure in the warehouse system three units away from the figure the website was showing on the fourteenth. Each of those questions is answerable in seconds if the flow was designed with evidence in mind, and takes two days of forensic work if it was not.
The important distinction is between reconstructing and proving. Reconstruction means going back to the source system today and inferring what must have happened. That is unreliable, because source records change. A Shopify order can be edited after capture; a customer address can be corrected; a product can be renamed or its SKU reassigned. If your only evidence is the current state of the source, you are not proving anything, you are guessing plausibly. Proving means holding a record, written at the time, of exactly what you read and exactly what you sent, in a store that nobody can quietly amend afterwards.
This matters commercially as well as technically. Disputes about integration behaviour tend to be expensive precisely because they are unresolvable: two parties each assert a version of events and neither can demonstrate it. A well-designed audit trail converts an argument into a lookup, which is usually the cheapest thing an integration ever does for you.
The record structure: correlation, identity and intent
Start from the unit of evidence. For financial and fulfilment flows, that unit is a message: one order read from a channel and posted to the ERP, one settlement line, one stock adjustment, one despatch notification. Every message gets a correlation identifier generated by the integration at the point of ingest, and that identifier travels with the data through every subsequent hop, appears in every log line, and is written into a reference field on the target record wherever the target permits it. If you can stamp the correlation ID into a user-defined field on a Sage 200 sales order or into an invoice reference in Xero, do so. That single act turns a ledger entry into a starting point for investigation rather than a dead end.
Around the correlation ID, record a fixed set of attributes for each message. Keeping this set small and stable is more valuable than making it comprehensive, because consistency is what makes the store searchable years later.
- Source identity and version — The source system, the source record identifier, and whatever version or concurrency token that system exposes — an updated_at timestamp, an ETag, a revision number. Without it you cannot distinguish a genuine second change from a re-read of the same state.
- Payload as read and payload as sent — Store both, compressed, exactly as they crossed the wire. The transformation between them is the part of the system most likely to be questioned, and a diff between the two answers most mapping disputes immediately.
- Mapping and scope version — A version string identifying the rule set in force when the message was processed. When somebody asks why March behaved differently from June, this field is the answer, and without it you are reading git history against release dates.
- Outcome and target identity — The HTTP status or API result, any error body, and the identifier the target system returned. A message that created Sage 200 sales order 00012345 should say so explicitly rather than requiring a join through three tables.
- Timings from two clocks — The event time reported by the source and the time your integration received it, both in UTC. Cross-system timestamp comparison is unreliable, and recording both makes clock drift visible instead of mysterious.
- Actor — Whether the message was triggered by a scheduled run, a webhook, or a human pressing a retry button, and which human. Manual intervention is where most anomalies originate and it is routinely unrecorded.
Write before you act, and never update in place
The sequencing of writes to the audit store is what separates a trail that survives failure from one that only works when everything succeeds. Write the intent record before making the outbound call: correlation ID, payload as sent, target endpoint, timestamp. Then make the call. Then write a separate outcome record referencing the same correlation ID. An intent with no outcome is not a gap in your data, it is a signal — it tells you that a call was despatched and the response was never received, which is exactly the scenario in which a duplicate order or invoice is created on the next attempt. If you only write after a successful response, those cases are invisible and you will spend the following morning reconciling by hand.
The store should be append-only. No updates, no deletes, no correcting a record because the first version was wrong. If a message is reprocessed, that is a new record with the same correlation ID and an incremented attempt number. Physically, this usually means a dedicated schema or database with a service account that holds insert and select rights and nothing else, separate from the operational database the integration uses for queueing and state. Two small disciplines make it defensible under scrutiny: a monotonic sequence number assigned by the store on insert, and a hash of the payload stored alongside the payload itself. The hash costs almost nothing and lets you demonstrate later that the stored copy has not been altered.
Retries are a specific hazard. Idempotency keys stop duplicate business records; they do not, on their own, produce an explanation. Record every attempt, including the ones the target rejected as duplicates, because the sequence of attempts is frequently the thing being questioned. A warehouse system such as Peoplevox acknowledging a despatch it has already seen is an unremarkable event in isolation and a meaningful one when it appears fourteen times in a minute.
Volume is rarely the obstacle people expect. A retailer processing five thousand orders a day, each generating an inbound payload, an outbound payload and a handful of status records, will accumulate in the region of twenty to thirty gigabytes a year once compressed. That is a trivial storage cost set against the cost of one unresolved VAT query. The discipline that does need enforcing is selectivity: capture full payloads for financially material and customer-visible flows, and capture summaries plus deltas for high-frequency polling traffic such as stock level checks, where retaining every unchanged snapshot buys nothing.
Retention, redaction and the awkward middle
Two obligations pull in opposite directions. VAT records in the UK generally need to be kept for six years, and the digital links expected under Making Tax Digital mean that the path from a transaction to a return should be traceable rather than re-keyed. At the same time, data protection principles require that personal data is not kept for longer than necessary. An audit store that holds complete order payloads containing names, addresses, email addresses and phone numbers for six years will satisfy the first obligation and create a problem against the second.
The workable pattern is staged redaction. Keep the complete payload for an operational window — ninety days is usually sufficient to cover disputes, chargebacks and reconciliation cycles. After that window, run a scheduled job that replaces designated personal fields with a null marker while leaving the financially material fields intact: order number, line items, quantities, net and gross values, tax codes, currency, exchange rate, target references. Because you hashed the original payload at write time, you can still demonstrate the integrity chain, and because redaction is itself an append-only event recorded in the store, you can show when and under what rule a field was removed.
Decide the redaction schedule during discovery, not after go-live, and document it as part of the scope. It is a business decision with legal implications, not an engineering preference, and it needs a named owner on the client side. It also needs testing: a redaction job with a wrong field path that quietly destroys tax codes instead of postcodes is a genuinely serious failure, and it should be exercised in acceptance testing against a seeded dataset with an expected before-and-after state.
Consider also where the store sits. If the audit store is inside the same account as the integration runtime and the same person can deploy code and administer the database, the separation is notional. For most SMEs that is an acceptable risk, provided it is stated. For regulated or investor-scrutinised businesses it is worth putting the store somewhere the integration can write to and the delivery team cannot administer, with access logged.
Making the integration audit trail usable by the people who need it
A trail that only an engineer can query is an incomplete deliverable. The realistic test is whether a finance assistant, given an invoice number from Sage 200 or a payment reference from Xero, can retrieve the originating channel order, see the payload as it arrived, see the transformation applied, and see every attempt made to post it, without raising a support ticket. That usually means a small internal lookup page with three search modes — by correlation ID, by source order reference, by target document number — and a date range. It does not need to be elegant. It needs to be available, permissioned, and documented in the handover pack with two or three worked examples.
Pair the lookup with a daily control that runs whether anyone is watching or not. Count messages by terminal state for the previous day, sum the gross value of documents created in the target, compare against the sum of documents read from the source, and list every exception with its correlation ID. Send that to a named person in finance rather than to a shared inbox. The discipline of a daily reconciliation figure that somebody signs off is worth more than any amount of alerting, because it catches the failure mode that alerting never catches: the integration that is running happily and processing the wrong subset of records.
Build the evidence requirement into acceptance testing explicitly. A useful acceptance criterion reads: select five orders at random from the previous week's production-like run, and for each one produce the source payload, the transformed payload, the mapping version, the target document reference and the full attempt history within two minutes. If that criterion cannot be met on the day of sign-off, it will not be met in nine months when it matters. We include it in the fixed scope for flows with a financial or fulfilment consequence, alongside the data mapping document, because it is cheap to build during the project and disproportionately expensive to retrofit.
Finally, treat the trail as part of the handover, not as internal tooling. The schema, the retention and redaction rules, the query examples and the daily control definition belong in the documentation handed over on completion, along with the code. The purpose of an integration audit trail is to make the business independent of the people who built the system — including us. If the evidence is only interpretable by its author, it is not evidence, it is a dependency.
Related platform guides
Key points
- Log intent before the outbound call and outcome after it, in an append-only store, so failed and duplicated attempts are visible rather than inferred.
- Stamp a correlation ID into the target record where the platform allows it, and store the payload as read, the payload as sent, and the mapping version in force at the time.
- Agree retention and staged redaction during discovery, and prove the trail during acceptance testing by retrieving five random orders end to end within two minutes.
Planning an integration?
Send us the two systems and the record types involved. We will come back with an outline scope and the approach we would recommend, within one working day.