Skip to content

The Integration Pattern

Applies to: e-Invoice · e-Way Bill · Which modules do I have?

Purpose

Give the build order every ERP integration follows, once, so each ERP-specific page describes only what is genuinely specific to it. Work through the steps in sequence: each one is verifiable on its own, and building them out of order produces a system where a failure could be in any of several places at once.

Audience

Integration developers and implementation consultants. Every step below assumes ERP-side development access.

Prerequisites

  • API Documentation read in full
  • Sandbox credentials
  • A raw sample payload per document type, exported from the ERP

Steps

1. Understand the shape before you build

The integration exists so that a document raised in the ERP becomes legally compliant without anyone re-keying it, and so the ERP holds the compliance outcome against its own record.

Without the integration With it
Manual export, upload and re-key Automatic
Compliance state lives only in Complifly Also in the ERP, against the invoice
Errors found at filing time Found at ingest, on the day
Effort scales with volume Effort is roughly constant

The flow it produces:

  ERP document posted
        |
        v
  [1] ERP emits its native payload
        |
        v
  [2] POST /api/v1/ingest/invoice/mapped/{templateId}
        |         with a machine credential
        v
  [3] Complifly: authenticate -> transform -> validate ->
        duplicate check -> route -> store
        |
        +--> rejected: ERP records the failure, a person acts
        |
        v
  [4] Accepted. invHdrId returned; ERP stores it
        |
        v
  [5] IRN generated (by a user, or automatically)
        |
        v
  [6] Write-back event emitted
        |
        v
  [7] ERP pulls the event, stores the outcome, acknowledges

Stages 1 to 4 are synchronous. Stages 5 to 7 are not. The ERP must not block waiting for a reference number — it arrives later, through the write-back feed.

Verify before moving on: everyone building this agrees which system holds which fact, and that a reference number is not available at the moment of posting.

2. Obtain a machine credential and prove it works

One credential per ERP, never shared, scoped to the registrations that ERP sends for. Full procedure in Machine-to-Machine Auth; the API view is in API Authentication.

Prove it with a low-risk call before writing anything else:

GET /api/v1/integration/health
X-API-Key: cfly_...

Verify before moving on: any response other than 401 proves authentication works — even 404 NO_SUBSCRIPTION. Only 401 means the credential is wrong.

3. Emit a raw payload from the ERP

Have the ERP produce its own native shape, unmodified. Do not rename fields toward NIC's names and do not tidy values by hand — the translation belongs in the mapping template, where it can be changed without an ERP release.

{
  "GSTNO": "29AAACW3775F000",
  "DOCTYPE": 0,
  "DOCNO": "INV-2026-0001",
  "DOCDATE": "5/26/2026 12:00:00 AM",
  "BUYER_GSTIN": "27AABCU9603R1ZM",
  "BUYER_NAME": "Example Buyer Pvt Ltd",
  "BUYER_STATE": "27",
  "TOTAL": 59000,
  "LINES": [
    { "SLNO": 1, "DESC": "Laptop", "HSN": "8471",
      "QTY": 2, "RATE": 25000, "AMOUNT": 50000, "GSTRATE": 18 }
  ]
}

The fields every integration must ultimately supply, whatever the ERP calls them:

Group Fields
Document Number, date, type, supply type, transaction type
Supplier Registration, legal name, address, location, postal code, state code
Buyer Registration, legal name, address, location, postal code, state code, place of supply
Values Assessable value, total value
Per line Serial number, description, goods-or-service flag, classification code, unit price, total, assessable amount, rate, total line value
Dispatch-from Only when goods leave from a different address — but once any part is used, the rest becomes required

The dispatch-from rule catches people out. It is optional as a block and mandatory as a whole: supply one field of it and you must supply the rest.

Verify before moving on: the sample is raw ERP output, and includes at least one multi-line document.

4. Build the mapping template

Map each ERP field onto its NIC counterpart, converting date formats and translating code values as you go.

Do this in the product, not in code. Build a Mapping Template is the screen-by-screen procedure — create the template, load your ERP's fields from the sample, drag them onto NIC fields, and set each rule to direct, date or lookup. Mapping Templates API covers the same ground for teams that would rather build templates programmatically.

Note the template ID it gives you. It goes in the ingest URL at step 5.

Verify before moving on: the mapped payload is correct field by field, checked against the transform preview — not merely that the document was accepted. Dates need a document actually posted, dated past the 12th of a month.

5. Send one document and handle every response class

POST /api/v1/ingest/invoice/mapped/12
X-API-Key: cfly_...
Content-Type: application/json

Accepted:

{ "status": "ACCEPTED", "invHdrId": 12345, "docNo": "INV-2026-0001",
  "gstin": "29AAACW3775F000", "fiscalYear": "2026-27", "itemsIngested": 1 }

Rejected:

{ "status": "REJECTED", "docNo": "INV-2026-0001", "docType": "INV",
  "errors": [ { "Level": "HEADER", "Field": "BillTo_Pos",
                "Message": "Place of Supply is required", "Severity": "ERROR" } ],
  "requestId": "…" }

Give every response class its own path in the ERP:

Response ERP behaviour
201 ACCEPTED Store invHdrId; mark as sent
422 REJECTED Store the errors; flag for a human; do not retry
409 DUPLICATE Reconcile against the returned identifier; do not retry
403 Alert an administrator — a scope problem, not a data problem
401 Refresh the credential; retry once
429 Back off; retry
5xx Back off; retry, bounded
Timeout Query status before resending

The 422 row is where integrations most often fall short. A rejection that only reaches a log file is a rejection nobody acts on, and the document silently never becomes compliant.

Verify before moving on: force each response class and confirm it takes the correct path — not just that the happy path works.

6. Retry correctly

Exponential backoff with jitter, bounded, plus a circuit breaker. Full policy in Rate Limits and Errors.

Never retry 422, 409 or 403. They cannot succeed, and they consume the rate-limit headroom genuine traffic needs.

Verify before moving on: an induced 422 is attempted exactly once, and a bounded retry storm cannot outlive the incident that caused it.

7. Consume the write-back feed

Outcomes reach the ERP through the write-back feed, on a schedule:

GET /api/v1/integration/events?since={cursor}&limit=100
  -> for each event, if not already processed (by event_id): store, mark processed
POST /api/v1/integration/events/ack  { "event_ids": [...] }
  -> only after the outcomes are durably committed
  -> store the new cursor
{ "event_id": "…", "event_pk": 4821, "event_type": "IRN_GENERATED",
  "gstin": "29AAACW3775F000", "doc_no": "INV-2026-0001", "irn": "…" }

Delivery is at-least-once, so the consumer must be idempotent, and acknowledgement must follow durable storage — acknowledge first and a crash loses the event permanently. Detail in Webhooks and Events.

Verify before moving on: a duplicate event is processed once, and a consumer killed mid-batch resumes from its cursor with nothing lost or repeated.

8. Build reconciliation before you need it

Three distinct failure classes, with three distinct recoveries:

Class Recovery
A document failed Correct it in the ERP and resend under the same number if it was never accepted, or a new number if it was accepted and registered
The integration stopped On restart, reconcile: for every document the ERP believes it sent since the outage, query status and resend only those absent
Write-back was missed Resume from the stored cursor. If events were dead-lettered, an administrator replays them

Build the reconciliation query into the integration from the start. It is the difference between a five-minute recovery and a day of manual comparison.

Verify before moving on: simulate an outage, run reconciliation, and confirm both systems agree afterwards with no duplicates created.

9. Test in this order

Stage What to test Where
Unit Payload construction, date formatting ERP development
Mapping Every field, using raw ERP samples Sandbox
Happy path One document end to end, ingest to write-back Sandbox
Edge cases Export, reverse charge, zero-rated, discount, rounding, multi-line, credit note, debit note Sandbox
Failure paths Every response class in the step 5 table Sandbox
Volume A realistic daily batch Sandbox
Recovery Simulate an outage; reconcile Sandbox
Business acceptance Real users, real documents Test environment

Edge cases are the ones customers do not volunteer and integrations always meet eventually. Ask for them by name.

Verify before moving on: every row above has been run and recorded, edge cases included.

10. Secure the integration

Concern Control
Credential storage A secret manager. Never in code or in source control
Transport HTTPS with certificate validation. Never disable it to work around a proxy
Scope One credential per ERP, scoped to its registrations only
Rotation Scheduled, owned, and rehearsed
Logging Never log credentials. Take care with personal data in payloads
Least privilege The credential needs ingest and write-back only, not administration
Inbound path Pull-based write-back needs no inbound path to the ERP — prefer it where the network policy is restrictive

Verify before moving on: no credential appears in source control or a log, and a rotation has been rehearsed rather than merely scheduled.

11. Monitor from both sides

Monitor from the ERP side as well as from Complifly. An integration that has stopped sending looks identical, from Complifly, to a quiet business day.

Signal Alert when
Documents sent per period Falls to zero during business hours
Rejection rate Rises above the normal baseline
Documents awaiting a reference number Age exceeds the reporting window's safety margin
Write-back backlog age Rising — the number that matters, not the count
Consecutive failures Threshold reached
Credential expiry Approaching

The third row is the one with a deadline attached. A document sitting unregistered is on a statutory clock, and when the clock runs out there is no remedy.

Verify before moving on: each alert has been fired in anger, not merely configured, and reaches a named person.

Validation

Check Pass condition
Mapped payload inspected Every field verified, not just acceptance
Every error class handled Each takes the correct path in the ERP
Nothing unretryable is retried Verified by forcing each response
Timeout reconciliation works Status queried before resend
Write-back consumer is idempotent A duplicate event is processed once
Outage recovery works Simulated, and both systems agree afterwards
Monitoring alerts fire Tested, not merely configured
A person owns rejections Named, with a process
Credentials in a secret manager Verified
Documentation handed over The customer can operate it unaided

Troubleshooting

Symptom Cause Action
Works in testing, fails on real documents Cleaned samples used Use raw ERP output
A wave of failures weeks after go-live A first edge case Test edge cases before go-live
Rejections invisible to the business Only logged Surface them to a person who can act
Duplicates after an outage Blind resend Reconcile by status first
ERP missing outcomes Write-back disabled, no subscription, or the consumer stopped Check in that order
Events processed twice Consumer not idempotent Deduplicate on the event identifier
Systems disagree on what was processed No reconciliation Build it
A brief outage becomes long Unbounded retries Bound them and add a circuit breaker
Nobody noticed the integration stopped No ERP-side monitoring Alert on documents sent falling to zero
Documents unregistered past the window Nobody watching the age of pending documents Alert on it. There is no remedy after the window closes