<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: nagaraju</title>
    <description>The latest articles on DEV Community by nagaraju (@nagaraju_debbb9d).</description>
    <link>https://dev.to/nagaraju_debbb9d</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4086702%2F65bd265d-eae4-418b-b5b4-a18143d403e9.png</url>
      <title>DEV Community: nagaraju</title>
      <link>https://dev.to/nagaraju_debbb9d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nagaraju_debbb9d"/>
    <language>en</language>
    <item>
      <title>Integrating an e-Invoice API in India</title>
      <dc:creator>nagaraju</dc:creator>
      <pubDate>Thu, 20 Aug 2026 14:37:08 +0000</pubDate>
      <link>https://dev.to/nagaraju_debbb9d/integrating-an-e-invoice-api-in-india-3b19</link>
      <guid>https://dev.to/nagaraju_debbb9d/integrating-an-e-invoice-api-in-india-3b19</guid>
      <description>&lt;p&gt;If you've ever been handed a ticket that says "integrate e-Invoicing" with zero context, you know the first hour is spent just figuring out what you're actually building. Not the compliance side — someone in finance already explained that. The integration side: what's the payload, what's the auth flow, and why does the sandbox behave differently from what the docs say.&lt;/p&gt;

&lt;p&gt;I've worked on a few of these integrations now (ERP-side and standalone billing systems), and this post is the write-up I wish I'd had before the first one. No fluff, just what actually matters when you're the one writing the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What "generate an e-Invoice" actually means as an API call&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Under GST rules, once a business crosses a turnover threshold, B2B invoices need to be registered with the government's Invoice Registration Portal (IRP) before they're legally valid. Registering one gets you back:&lt;/p&gt;

&lt;p&gt;an IRN (Invoice Reference Number) — a unique hash tied to that invoice&lt;br&gt;
a QR code that has to appear on the printed/PDF invoice&lt;br&gt;
signed invoice data you need to store as proof of registration&lt;/p&gt;

&lt;p&gt;So functionally, you're building a generateIRN(invoicePayload) call that sits between "invoice created in your system" and "invoice sent to customer." Simple in concept. The payload itself, though, has a lot of surface area — supplier/buyer GSTIN and address details, line-item HSN/SAC codes, tax breakdowns per line, and totals that all have to reconcile exactly, or the IRP rejects the whole thing.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;The stuff that isn't in the "getting started" docs&lt;br&gt;
*&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Timeouts don't mean failure. This one bit me early. If a generateIRN call times out on your end, that does not mean it failed on the IRP side. If you blindly retry, you risk generating two IRNs for the same invoice — which is its own mess to clean up. The right move is: on ambiguous failure, call the "get IRN by document details" lookup first, and only resubmit if it genuinely wasn't registered.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;js&lt;br&gt;
async function safeGenerateIRN(invoice) {&lt;br&gt;
  try {&lt;br&gt;
    return await generateIRN(invoice);&lt;br&gt;
  } catch (err) {&lt;br&gt;
    if (isTimeoutOrAmbiguous(err)) {&lt;br&gt;
      const existing = await getIRNByDocDetails(invoice.docNumber, invoice.docDate);&lt;br&gt;
      if (existing) return existing;&lt;br&gt;
    }&lt;br&gt;
    return await generateIRN(invoice); // safe to retry now&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Cancellation has a window and preconditions. You can't cancel an e-Invoice whenever you want — there's a limited time window, and if an E-Way Bill is already linked to it, that has to be handled first. Check eligibility client-side before calling the cancel endpoint, or you'll be surfacing a confusing IRP rejection to whoever's using your app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rate limits mean you need a queue, not just a retry loop. If your system can create invoices faster than the IRP will accept IRN submissions (very possible during month-end batch runs or a flash sale), you need actual backpressure handling — a queue that submits within rate limits — not a for loop with await and a prayer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Real-time validation saves you from yourself. Validate HSN codes, tax rates, and place-of-supply logic before you submit, using the same rules the IRP enforces. Catching an error client-side takes milliseconds. Catching it after an IRP rejection means parsing their error response and mapping it back to a field — every provider's error codes are a little different, and not always self-explanatory.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sandbox vs. production: what actually changes&lt;/p&gt;

&lt;p&gt;Sandbox is fine for validating your payload shape and auth flow. It will not prepare you for:&lt;/p&gt;

&lt;p&gt;realistic rate limiting under load&lt;br&gt;
what happens when the IRP itself is slow (it happens, especially near filing deadlines)&lt;br&gt;
multi-GSTIN session handling, if your business operates across states&lt;/p&gt;

&lt;p&gt;If you're building for a business with more than one GSTIN, isolate sessions/credentials per GSTIN from day one. I've seen a setup where a token issue on GSTIN #1 silently backed up invoice queues for GSTIN #2 and #3 — because auth was treated as global instead of per-entity. Painful to debug in production, trivial to avoid in the initial design.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;A minimal mental model&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
If I had to summarize the whole integration in one diagram:&lt;/p&gt;

&lt;p&gt;[Invoice created] &lt;br&gt;
      ↓&lt;br&gt;
[Client-side validation: HSN, tax rate, place of supply]&lt;br&gt;
      ↓&lt;br&gt;
[Submit to IRP via generateIRN — queued, rate-limit aware]&lt;br&gt;
      ↓&lt;br&gt;
  success? → [Store IRN + QR + signed data] → [Sync back to invoice record]&lt;br&gt;
  ambiguous failure? → [Lookup by doc details] → [retry only if truly missing]&lt;br&gt;
  hard failure? → [Surface specific validation error to user]&lt;/p&gt;

&lt;p&gt;Everything else — cancellation, bulk generation, E-Way Bill linkage — builds on top of this same core loop.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Wrapping up&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
None of this is exotic engineering. It's mostly just knowing the failure modes ahead of time instead of discovering them in production during month-end billing. If you're picking a provider to build on top of, the thing worth actually evaluating isn't the happy-path demo — it's how well they document the retry logic, cancellation rules, and rate limits, because that's what you'll actually be writing code against.&lt;/p&gt;

&lt;p&gt;I work with e-Invoice/GST/E-Way Bill APIs fairly often at &lt;a href="https://www.perione.in/" rel="noopener noreferrer"&gt;PeriOne&lt;/a&gt; — happy to answer questions in the comments if you're mid-integration and stuck on something specific.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>api</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
