Developer API and webhooks
Read invoices and credit notes from your own tools, and get notified when one is issued.
The developer API lets your own tools, an accounting sync or an ERP read the invoices and credit notes InvoiceHop issued, and download their PDF and e-invoice XML. It's read-only: documents are still created and corrected in InvoiceHop. Included in Pro and Pro Plus.
Create a key
In Settings → Developer API, enter a name and click Create key. Copy the key right away: it starts with ih_live_ and is shown only once. You can have up to 10 keys and revoke any of them at any time. Send the key in the Authorization header of every request:
curl https://invoicehop.com/api/v1/documents \ -H "Authorization: Bearer ih_live_…"
Each key can make up to 120 requests a minute. Past that the API answers 429 with a Retry-After header.
Endpoints
GET /api/v1/documents: invoices and credit notes, newest first. Filters:type(invoiceorcredit_note),status(draft,issued,credited,void),order_id(Shopify's numeric order id),fromandto(issue date, YYYY-MM-DD) andlimit(up to 100, 50 by default).GET /api/v1/documents/:id: one document with its lines and VAT breakdown.GET /api/v1/documents/:id/pdf,/ubl,/ciior/xrechnung: the files. Issued documents return the exact PDF frozen when they were issued. Drafts have a PDF but no XML yet.
Lists come in pages. When next_cursor isn't null, ask for the next page with ?cursor= and the same filters.
{
"data": [
{
"id": "cm…",
"type": "invoice",
"status": "issued",
"number": "INV-2026-00042",
"issue_date": "2026-10-14",
"currency": "EUR",
"total_excl_tax": 100,
"total_tax": 20,
"total_incl_tax": 120,
"order": { "id": "5512345678901", "name": "#1042" },
"buyer": { "name": "Acme GmbH", "business": true, "vat_number": "DE123456789", … },
"lines": [ … ],
"vat": [ { "category": "S", "rate": 20, "taxable": 100, "tax": 20 } ],
"files": { "pdf": "…/pdf", "ubl": "…/ubl", "cii": "…/cii", "xrechnung": "…/xrechnung" }
}
],
"next_cursor": null
}Errors come back as { "error": { "code": "…", "message": "…" } }: 401 for a missing or revoked key, 402 when the store isn't on a paid plan, 404 for an unknown document, 409 for the XML of a draft.
Webhook
Under Webhook, enter an https address and save. Each time an invoice or credit note is issued, InvoiceHop sends it a POST with a document.issued event holding the same document as the API. If your server doesn't answer with a 2xx status, we try again for about an hour. The event id stays the same across retries, so you can ignore one you already handled. Send test event sends a ping event.
Every request has an X-InvoiceHop-Signature header like t=1760000000,v1=5f2b…. To check it comes from us, compute the HMAC-SHA256 of t, a dot and the raw request body, using the signing secret shown in the settings, and compare it with v1. Reject events whose t is more than five minutes old.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return v1?.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}If the secret leaks, click Regenerate secret and update it on your server.