Write-back Failures¶
Applies to: ERP Write-back · Which modules do I have?
Purpose¶
Diagnose an ERP that does not have the compliance outcomes it should, and recover without creating a second incident by replaying too much at once.
Audience¶
Support teams, integration developers, administrators.
Prerequisites¶
- The registration and the document numbers concerned
- Access to the write-back health and delivery logs
- Write-back API
Steps¶
The decision tree¶
The ERP is missing compliance outcomes
|
v
Is write-back enabled for this registration?
|
+-- No ---> That is the answer. Enabling it does NOT backfill. -> Path A
|
+-- Yes -> Is there an active subscription?
|
+-- No ---> Configuration. -> Path B
|
+-- Yes -> Are events being produced?
|
+-- No ---> Are the documents actually
| registered? If not, this is an
| IRN problem, not a write-back one
|
+-- Yes -> Is the backlog age rising?
|
+-- Yes --> Path C (delivery stalled)
|
+-- No ---> Events delivered.
The ERP consumed and
discarded them. -> Path D
The first two branches account for a large share of these tickets, and both are configuration rather than failure.
The most misleading case: everything looks enabled and nothing is emitted. A subscription is required per ERP connection per registration, and it is checked at emission — not at delivery. Without one, a document from that ERP produces no event at all: nothing queued, nothing skipped, nothing to find later. There is no error on either side.
This is not theoretical. A live ERP ran for days with its connection active, its documents correctly tagged and both feature flags on, emitting nothing, because the one subscription row had never been created for it.
The trap is that another ERP on the same platform may be working perfectly, which makes the feature look healthy. A subscription belongs to one connection, so a second ERP — even on the same company, even on the same GSTIN — needs its own.
Check the subscription for the specific connection, not just "is there a subscription on this GSTIN".
Path A — write-back not enabled¶
| Step | Action |
|---|---|
| Symptoms | The ERP has never received anything |
| Root cause | The feature is off by default |
| Checks | Check the flag for each registration concerned |
| Resolution | Enable it. Emission is not retrospective — no events exist for anything that happened while it was off |
| Validation | A newly processed document produces an event |
| Escalate when | Historical state must be seeded — that is a separate, explicit exercise |
Path B — no subscription for this connection¶
| Step | Action |
|---|---|
| Symptoms | The pull feed returns a no-subscription response — or, more confusingly, returns success with nothing in it while the ERP's documents are being processed normally |
| Root cause | No active subscription for this connection and this registration. A subscription on the same GSTIN belonging to a different ERP does not serve this one |
| Checks | Which connection is this ERP? Which registrations does it cover? Does each of those have a subscription against that connection? Is the subscription active, or paused? |
| Resolution | Configure a subscription for the connection. Ask Complifly — an administrator can see, on one screen, every connection that has no subscriber |
| Validation | Process one document and confirm an event appears for it |
| Escalate when | An active subscription exists for the right connection and the feed is still empty |
Emission is not retrospective here either. Creating the subscription starts the feed from that moment; it does not produce events for documents processed before it existed. If the ERP needs its earlier state, say so — seeding is a separate, explicit exercise.
A paused subscription behaves differently and is worth ruling out: events keep accumulating while it is paused and drain when it resumes. An empty feed therefore means "no subscription", whereas a stalled-but-growing backlog means "paused or failing" — Path C.
This is a configuration state, not a transient one. An integration retrying it indefinitely is a common defect; it will never succeed on its own.
Path C — delivery stalled¶
| Step | Action |
|---|---|
| Symptoms | Backlog age rising |
| Root cause | For pull: the consumer stopped, or is not acknowledging. For push: the endpoint is failing and the circuit has opened |
| Checks | Is the consumer running? Is it acknowledging? What do the delivery logs show? Is the circuit open? Are events dead-lettered? |
| Resolution | Restart or fix the consumer, or fix the endpoint. Delivery resumes after cooldown. Replay anything dead-lettered |
| Validation | Backlog age falls and stays low |
| Escalate when | The consumer and endpoint are both demonstrably healthy |
Diagnose on age, not count. A steady count can hide a stuck head-of-queue indefinitely.
Path D — delivered, but the ERP does not have it¶
| Step | Action |
|---|---|
| Symptoms | Events show as delivered; the ERP has nothing |
| Root cause | The consumer acknowledged without storing — commonly acknowledging before committing, or failing silently while processing |
| Checks | Does the consumer acknowledge before or after committing? Does it swallow exceptions? Does it treat unknown event types as fatal? |
| Resolution | Fix the ordering: commit, then acknowledge. Then reconcile and replay what was lost |
| Validation | A test event survives a deliberate consumer restart |
| Escalate when | The consumer is provably correct |
This is the most damaging failure in the set, because everything appears healthy on both sides while data is being silently discarded.
Recovering safely¶
| Situation | Recovery |
|---|---|
| Consumer stopped briefly | Resume from the cursor. Nothing lost |
| Cursor lost | Restart from zero; idempotency discards duplicates |
| Events dead-lettered | An administrator replays them |
| A large backlog | Preview the requeue. Pause the consumer. Requeue in batches. Resume |
| Uncertain what was missed | Reconcile by document status rather than replaying blindly |
Always preview before requeuing. A requeue with a wider scope than intended floods a consumer that may not be ready, turning one incident into two.
The retention trap¶
Dead-lettered events are retained for a configured period. If that period is shorter than your realistic reaction time, the evidence is purged before anyone investigates — a weekend outage with a two-day retention loses everything.
Check the setting against how quickly your team actually responds, not how quickly it intends to.
Validation¶
| Check | Pass condition |
|---|---|
| The flag and subscription checked first | Before any deeper investigation |
| Diagnosis used backlog age | Not count |
| The consumer's ordering verified | Commit before acknowledge |
| Recovery previewed | Before requeuing |
| Idempotency confirmed | Replayed events processed once |
| Reconciliation performed | Both systems agree afterwards |
| Retention adequate | Exceeds realistic reaction time |
Troubleshooting¶
| Symptom | Cause | Action |
|---|---|---|
| The ERP has never had outcomes | Write-back off | Enable it. Not retrospective |
| No events for old documents after enabling | Emission is not retrospective | Expected. Seeding is separate |
| Feed returns no-subscription forever | Configuration, retried as transient | Configure a subscription |
| Backlog age rising, count steady | Stuck head-of-queue | Investigate the oldest event specifically |
| Delivery stopped after a burst of failures | Circuit breaker opened | Expected. Fix the endpoint |
| Events delivered but absent from the ERP | Acknowledged before committing | Reverse the order, then reconcile and replay |
| Events processed twice | Consumer not idempotent | Deduplicate on the event identifier |
| Consumer fails on a new event type | Unknown types treated as fatal | Log and skip unknown types |
| Dead-lettered events gone | Retention too short | Raise it |
| A requeue flooded the consumer | No preview | Preview; pause the consumer for a large replay |
| Nobody noticed delivery had stopped | No alert, or addressed to an individual | Alert on backlog age, to a distribution list |