What this does
Every invoice and credit note Invocourier issues is kept in the app's archive, and you can download it all at any time. An archive destination goes one step further: the moment a document is issued, a copy (the XML, plus the PDF where one exists) is pushed to storage you control. You are never dependent on the app to hold your legal records, and the copies keep arriving without anyone doing anything.
Available today: a signed webhook (Zapier, Make, n8n, your own endpoint), an S3-compatible bucket (Amazon S3, Hetzner, Cloudflare R2, Scaleway, OVHcloud, Backblaze, Wasabi, DigitalOcean, MinIO) an SFTP server, the cloud drives Google Drive, Dropbox and OneDrive, and WebDAV (Nextcloud, ownCloud, Synology, Storage Box). Automatic archive copies by email exist as well and are independent of this.
Pushes are best-effort by design: they never delay or block invoicing, and the archive in the app stays the authoritative record. A destination that fails is retried for several days and then shown on the Home page.
Setting it up
Open the app and go to Archive destinations (also linked from Settings, next to the archive email address).
- Under Add a destination, pick the type and click Add.
- Fill in the destination's details (for a webhook: the endpoint URL) and click Save and test.
- The app runs a connection test right away. When it passes, the destination shows Active and receives every document issued from then on. When it fails, the destination's answer is shown and nothing is sent until a test passes.
Every change to the connection details puts the destination back to Test pending, so a typo can never silently send documents nowhere. Pause keeps the settings but stops sending; Remove deletes them.
Credentials and secrets are stored encrypted and never shown in the app except where you ask for them (the webhook's signing secret).
Webhook
One HTTPS POST per document to the URL you enter. Only https:// URLs on public hosts are accepted. The request is signed with a secret that was generated when you created the webhook; it stays the same when you change the URL, so your receiver only needs it once. Show signing secret on the webhook's edit form reveals it.
Headers
| Header | Value |
|---|---|
Content-Type | application/json |
X-Invocourier-Event | document.issued, or ping for the connection test |
X-Invocourier-Delivery | An identifier for this attempt (a retry gets a new one) |
X-Invocourier-Timestamp | Unix time in seconds when the request was signed |
X-Invocourier-Signature | v1= followed by the hex HMAC (see below) |
User-Agent | Invocourier-Webhook/1 |
Body
{
"event": "document.issued",
"shop": "your-store.myshopify.com",
"sentAt": "2026-09-09T10:15:02.000Z",
"document": {
"id": 12,
"number": "INV-12",
"type": "invoice", // or "credit_note"
"correctsNumber": null, // credit notes: the corrected invoice's number
"orderName": "#1012",
"orderId": "gid://shopify/Order/1234567890",
"issueDate": "2026-09-09",
"profile": "peppolBis3", // peppolBis3 | xrechnung | en16931
"currency": "EUR",
"totalExclVat": "100.00",
"totalVat": "21.00",
"totalInclVat": "121.00",
"buyerName": "Buyer BV",
"buyerVatId": "BE0123456749"
},
"files": [
{ "name": "INV-12.xml", "contentType": "application/xml", "size": 4821, "content": "PD94bWwg..." },
{ "name": "INV-12.pdf", "contentType": "application/pdf", "size": 61230, "content": "JVBERi0x..." }
]
}
content is the file, base64-encoded. The XML is always there; the PDF only for profiles that produce one (Germany's XRechnung/Factur-X hybrid and PDF invoices to consumers). The ping test carries only event, shop and sentAt.
Your endpoint must answer with a 2xx status within 15 seconds. Anything else, including a redirect, counts as a failure and is retried. Answer first, process later if your processing is slow.
Verifying the signature
Recommended whenever your endpoint is reachable by anyone (a plain URL is not a secret). Compute HMAC-SHA256(secret, timestamp + "." + rawBody) where timestamp is the header value and rawBody the request body exactly as received, and compare it in constant time with the hex after v1=. Reject requests whose timestamp is more than five minutes off your clock, which defeats replays. In Node.js:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret, headers, rawBody) {
const ts = Number(headers["x-invocourier-timestamp"]);
if (!Number.isInteger(ts) || Math.abs(Date.now() / 1000 - ts) > 300) return false;
const expected = "v1=" + createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
const given = headers["x-invocourier-signature"] ?? "";
return expected.length === given.length && timingSafeEqual(Buffer.from(expected), Buffer.from(given));
}
Zapier, Make and n8n "catch hook" URLs contain a long random token and are only known to you, so verification is optional there.
Recipes
- Zapier: Webhooks by Zapier → Catch Hook (raw). Add a Formatter → Utilities → Base64 decode (or a Code step) on
files[0].content, then Google Drive → Upload File with the decoded content andfiles[0].nameas the file name. Repeat for the PDF. - Make: Webhooks → Custom webhook, then Tools → Base64 decode (or the
toBinary(…; base64)function) and any storage module (Google Drive, Dropbox, OneDrive, SFTP, S3). - n8n: Webhook node (respond immediately), Convert to File node with "Move Base64 String to File", then the storage node of your choice.
- Bookkeeping tools: most accept documents by email; the archive email copy is the simpler route there.
S3-compatible bucket
Each document is uploaded as an object named prefix/YYYY/MM/INV-12.xml (and .pdf where one exists), so a year is one folder and a month one subfolder. Pick your provider and the endpoint, region and addressing style are filled in; you add the bucket name, an optional folder prefix and an access key pair.
| Provider | What to know |
|---|---|
| Amazon S3 | Endpoint https://s3.<region>.amazonaws.com; the region field must match the bucket's region. Create an IAM user with s3:PutObject (and s3:DeleteObject for the test cleanup) on the bucket. |
| Hetzner Object Storage | Endpoint per location (fsn1, nbg1, hel1), region = the location name, S3 credentials from the Cloud Console. |
| Cloudflare R2 | Endpoint https://<account id>.r2.cloudflarestorage.com, region auto, an R2 API token with object read & write on the bucket. |
| Scaleway, OVHcloud, Backblaze B2, Wasabi, DigitalOcean Spaces | Presets filled in for their European regions; change endpoint and region together if your bucket is elsewhere. Backblaze: use an application key restricted to the bucket, its key ID as access key ID. |
| MinIO, Garage, Ceph, other | Enter the endpoint and region your server expects (often us-east-1); keep path-style addressing on. |
Only https:// endpoints are accepted. Requests are signed with AWS Signature Version 4; the app never lists or reads your bucket. The connection test writes invocourier-connection-test.txt under the prefix and deletes it again (a key that may write but not delete still passes; the file stays).
SFTP server
Files are written to folder/YYYY/MM/ over SSH, with a password or a private key (PEM or OpenSSH format, optional passphrase); missing directories are created. Any SFTP server works: your web host, a NAS, a Hetzner Storage Box, a server of your own. Plain FTP is deliberately not offered.
Host key: the first successful connection test records the server's key fingerprint and every later upload checks it. If the server is reinstalled and its key changes, uploads stop with a clear error until you run the connection test again, which accepts the new key and shows its fingerprint.
The connection test logs in, creates the folder if needed, writes invocourier-connection-test.txt there and removes it.
Google Drive, Dropbox, OneDrive
The cloud drives connect with a click instead of credentials: Connect Google Drive (or Dropbox, OneDrive) opens the vendor's consent screen at the top of your browser, you approve, and you land back in the app with the destination already tested and active. The access is as narrow as each vendor allows:
- Google Drive: the "files created by this app" permission only. Documents go into a folder in your My Drive (default Invocourier, name yours to choose), then
YYYY/MM. Invocourier cannot see or list any other file in your Drive. - Dropbox: the app-folder permission. Everything is written under
Apps/Invocourier/(optionally a subfolder you name), thenYYYY/MM; nothing outside that folder is visible to the app. - OneDrive (personal or Microsoft 365): the app-folder permission via Microsoft Graph, so everything lives under
Apps/Invocourier/too.
The vendor gives Invocourier a long-lived token that is stored encrypted and refreshed automatically. If you remove the app's access in your Google, Dropbox or Microsoft account settings, uploads stop with a clear message and Reconnect in the app restores them. Google also expires an unused connection after six months of inactivity; a store with regular invoices never hits that.
Renaming or moving the folder later is fine for Google Drive (the app remembers the folder itself) and irrelevant for the app folders. Deleting the folder makes the app create it again on the next document.
WebDAV (Nextcloud, ownCloud, Synology, Storage Box)
Enter the WebDAV address of the folder the documents should go into, plus username and password; files are written to YYYY/MM/ below it, folders created as needed. Only https:// addresses are accepted.
| Server | Folder URL to enter |
|---|---|
| Nextcloud, ownCloud | https://cloud.example.com/remote.php/dav/files/USERNAME/Invoices/ (Files app → Settings, bottom left, shows your WebDAV base). With two-factor authentication on, create an app password under Settings → Security and use that. |
| Synology NAS | Enable the WebDAV Server package (HTTPS port 5006): https://nas.example.com:5006/Invoices/, with a user that has write access to that shared folder. |
| Hetzner Storage Box | Enable WebDAV in the Robot panel: https://uXXXXXX.your-storagebox.de/Invoices/, username uXXXXXX. |
| IONOS HiDrive, other | The WebDAV URL your provider documents, ending in the folder you want. |
The connection test checks the folder (creating it, and missing parents, when it does not exist yet), writes invocourier-connection-test.txt and removes it. A wrong password shows as "authentication refused"; a non-WebDAV address as "method not allowed".
Retries, failures, and what you see
Each document is pushed the moment it is issued. If that fails, it is retried after 5 minutes, 30 minutes, 2 hours, 12 hours, then daily, for roughly three and a half days in total. A push that still fails after that is marked failed; Home shows how many documents are affected and the Archive destinations page has a Retry failed copies button that gives them a fresh run once you have fixed the receiver.
After three failures in a row the destination itself shows Failing with the last error; it goes back to Active by itself on the next successful push. Each destination shows how many documents were copied, are queued and have failed.
A failing destination never affects invoicing or delivery to your buyers, and the archive in the app always holds every document.
Copying existing documents
A destination only receives documents issued after it became active. Copy existing documents queues everything already in the archive that the destination has not received yet; the copies run in the background, about 50 every five minutes, and each document is pushed to a given destination at most once.
Data protection
Pushing documents to a destination happens on your documented instruction, into storage you own or have contracted yourself. The receiver is therefore your own processor (or you), not a sub-processor of Invocourier; make sure your agreement with that provider covers invoice data. Only encrypted transport (HTTPS) is used; plain FTP is deliberately not offered.
Removing a destination deletes its settings and secrets at once; documents already delivered to it stay where you put them. Everything is also deleted when you uninstall the app.