Live · HMAC-verified webhooks · Idempotent

Shopify GST Invoicing App

Auto-generate GST-compliant invoices for every Shopify order. CGST/SGST/IGST is derived from the shipping address, buyer GSTIN flows from checkout note attributes, and every webhook is HMAC-verified per store.

Endpoint: POST /api/public/v1/shopify/webhook

Zero-touch invoicing

An invoice is created the moment Shopify marks an order as paid.

Correct tax split

CGST/SGST when shipping state == your state, IGST otherwise. Always.

Per-store HMAC secret

Each store gets its own signing secret. Mismatch = 401.

Idempotent

Retries from Shopify never produce duplicate invoices.

Setup in 4 steps

From sign-in to your first auto-generated invoice in under five minutes.

  1. 1

    Configure your supplier details

    In Settings, save your company name, GSTIN, and state. Your state determines the CGST/SGST vs IGST split for every Shopify order.

  2. 2

    Connect your Shopify store

    Open Shopify, click Connect store, and paste your <store>.myshopify.com domain. We'll issue a webhook URL and a per-store signing secret (shown once).

  3. 3

    Register the webhook in Shopify

    In Shopify Admin, go to Settings → Notifications → Webhooks (or via Admin API). Create a webhook for the orders/paid topic, format JSON, URL and secret from step 2.

  4. 4

    Place a test order

    Mark a test order as paid in Shopify. Within seconds, the invoice appears under Invoices with the correct tax split. The Last event column on the Shopify page updates too.

Authentication

There are two distinct authentication boundaries to think about.

1. Dashboard (you ↔ us)

You sign in to your GST Invoice account with email/password or Google. All store management — connecting stores, rotating secrets, toggling auto-create, viewing invoices — is gated by your session and protected by row-level security.

2. Webhook (Shopify ↔ us)

Shopify authenticates to our webhook endpoint by signing each request with a per-store secret. We verify the signature on the raw body before parsing — no signature, no processing.

Required webhook headers

HeaderPurpose
X-Shopify-Shop-DomainYour store domain, e.g. my-store.myshopify.com — used to look up the signing secret.
X-Shopify-TopicEvent topic. We act on orders/paid and orders/create (per your settings).
X-Shopify-Hmac-Sha256Base64 HMAC-SHA256 of the raw request body using the per-store signing secret. Verified server-side; mismatches return 401.
Content-Typeapplication/json — Shopify sends the order payload as JSON.

Reference: HMAC verification

You don't need to run this — we verify it server-side. It's here so you can audit the algorithm or replay payloads in a local proxy.

// Node.js — verify Shopify HMAC before processing
import { createHmac, timingSafeEqual } from "crypto";

function verify(rawBody, header, secret) {
  const computed = createHmac("sha256", secret).update(rawBody, "utf8").digest("base64");
  const a = Buffer.from(computed);
  const b = Buffer.from(header ?? "");
  return a.length === b.length && timingSafeEqual(a, b);
}

Security model

Per-store signing secret

A unique high-entropy secret is generated when you connect each store. Secrets are stored encrypted and shown to you exactly once.

Raw-body HMAC verification

We compute HMAC-SHA256 over the unparsed request body and compare with a timing-safe equality check. Tampered payloads are rejected with 401.

Idempotency keys

We enforce a unique constraint on (shop_domain, shopify_order_id). Shopify's at-least-once retries are safe by construction.

Topic gating

Only the topics you've explicitly enabled (orders/paid and/or orders/create) are processed. Anything else returns 200 skipped.

Secret rotation

Rotate a secret any time from the dashboard. The old secret stops working immediately — update Shopify with the new one.

Replay-safe processing

Even if Shopify replays an old, valid payload, idempotency prevents duplicate invoices. Old store disconnects return 404 shop_not_connected.

Treat the signing secret like a password.

Anyone with the secret can post invoice-creating webhooks for your store. Don't paste it into chat, screenshots, or shared docs. If exposed, rotate immediately.

Webhook payload & responses

We accept Shopify's standard order JSON — no custom transformation required on your side.

Sample inbound webhook

// Example: Shopify orders/paid → our /api/public/v1/shopify/webhook
// Headers (sent by Shopify, verified by us):
//   X-Shopify-Shop-Domain: my-store.myshopify.com
//   X-Shopify-Topic:       orders/paid
//   X-Shopify-Hmac-Sha256: <base64 hmac>
//
// Body (excerpt — Shopify's standard order JSON):
{
  "id": 8123456789,
  "currency": "INR",
  "total_price": "5900.00",
  "line_items": [
    { "title": "Cotton Tee", "quantity": 2, "price": "2500.00", "sku": "TEE-M-BLK" }
  ],
  "shipping_address": { "province_code": "KA", "country_code": "IN" },
  "note_attributes": [
    { "name": "gstin", "value": "29AAGCB1234F1Z5" },
    { "name": "place_of_supply", "value": "29" }
  ]
}

Responses

// 201 Created — invoice generated
{
  "ok": true,
  "invoice_id": "5b1e...c0",
  "invoice_number": "INV-2026-000142",
  "warnings": []
}

// 200 OK — duplicate (idempotent retry)
{ "ok": true, "duplicate": true, "invoice_id": "5b1e...c0" }

// 401 Unauthorized — HMAC mismatch
{ "error": { "code": "invalid_hmac", "message": "Webhook signature verification failed" } }

Note attributes we read

Set these in Shopify checkout (Liquid, Checkout UI Extensions, or via the Storefront API). Any unrecognised attributes are ignored.

KeyMeaning
gstinBuyer GSTIN (15 chars). Stored on the invoice; enables B2B treatment.
place_of_supplyOptional override — 2-digit GST state code (e.g. 27 for Maharashtra). Defaults to the shipping address province.
legal_nameOptional — overrides the customer name printed on the invoice (useful for company billing names).

State code mapping (sample)

Shopify province_code is translated to the 2-digit GST state code we need for the place of supply.

Shopify codeGST state codeState
MH27Maharashtra
KA29Karnataka
DL07Delhi
TN33Tamil Nadu
GJ24Gujarat
TG36Telangana
WB19West Bengal
UP09Uttar Pradesh
KL32Kerala

All 36 Indian states and UTs are supported — these are just examples. Override anything with the place_of_supply note attribute.

FAQ

Ready to auto-invoice every Shopify order?

Connect a store, paste one webhook, and you're done.