Mavri Get started

Automation

Using invoice software with a REST API and webhooks

Create invoices from orders, sign a webhook, and keep API keys safe, with examples checked against a running Mavri.

By Raktim Ranjit ·

Short answerMake an API key in Settings → API keys, send orders to POST /api/v1/orders with a Bearer token, or sign a JSON body with your store’s secret and post it to a webhook URL. Repeats are safe: the same order ID never creates a second invoice.

Two ways in

Mavri’s API is served by your own installation, so every URL below points at your server, not at a hosted service. There are two common routes for turning outside events into invoices:

  • The REST API, for scripts and tools you control. You authenticate with an API key.
  • Signed webhooks, for shops that push orders to you. The sender signs the request with a shared secret instead of using an API key.

1. Make an API key

Open Settings → API keys and create one. The secret looks like mvr_… and is shown once, so store it in a password manager or your secret store. Keys can do everything except manage keys and restore backups. Treat one like a password: keep it out of source control and out of browser code.

2. Create an invoice from an order

First connect a store on the Stores page. A store connection supplies the business, currency, bank account, style and default tax, and gives you its shopId. Then:

curl -X POST http://localhost:3001/api/v1/orders \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"shopId":1,"externalOrderId":"ORD-1001","customer":{"name":"Acme Ltd","email":"billing@acme.test"},"lineItems":[{"name":"Consulting","quantity":2,"unitPriceMajor":150}]}'

The reply is {"documentId": 42, "duplicate": false} with status 201. Send the same externalOrderId again and you get the same invoice back with "duplicate": true and status 200. That idempotency means a retrying client cannot double-bill.

3. Signed webhooks

Shops post orders to /api/hooks/{platform}/{shopId}. The platform is shopify, woocommerce or generic. The raw request body must be signed with the connection’s secret using HMAC-SHA256:

PlatformHeaderEncoding
shopifyX-Shopify-Hmac-Sha256base64
woocommerceX-WC-Webhook-Signaturebase64
genericX-Webhook-Signaturehex

Shopify and WooCommerce already sign this way, so you paste the webhook URL and secret into their settings. For anything else, use generic. The body is the same JSON as the orders endpoint, without shopId. A small signer in Python:

import hmac, hashlib, json, urllib.request

secret = b"YOUR_STORE_SIGNING_SECRET"
body = json.dumps({"externalOrderId": "ORD-1002",
  "customer": {"name": "Acme Ltd"},
  "lineItems": [{"name": "Consulting", "quantity": 1, "unitPriceMajor": 150}]}).encode()
sig = hmac.new(secret, body, hashlib.sha256).hexdigest()
req = urllib.request.Request("http://localhost:3001/api/hooks/generic/1", data=body,
  headers={"Content-Type": "application/json", "X-Webhook-Signature": sig})
print(urllib.request.urlopen(req).read())

Sign the exact bytes you send; re-serialising the JSON after signing changes the signature. A wrong signature returns 401, a paused connection 403, and each connection is limited to 120 requests a minute.

Good habits

  • Use HTTPS for anything reachable beyond your own network.
  • Money is in minor units. Fields ending in Cents are whole cents; the order endpoint takes major units (unitPriceMajor).
  • Read errors. Failures come back as {"error": {"code", "message"}} with a matching status such as 400, 404, 409 or 422.
  • One key per tool, so you can delete a single one if it leaks.

The MCP server

For AI clients there is a separate program, mavri-mcp, a stdio MCP server in the same image. It reads an API key from MAVRI_API_KEY and exposes tools such as listing and creating documents, recording a payment and exporting an e-invoice. Give an assistant a key only if you are comfortable with it acting on your invoices.

Sources

About the authorRaktim Ranjit created Mavri and leads the project at NodeDR Infotech Private Limited. See About.

More from the blog

Invoicing

Quote vs. invoice: when to use each

What separates a quote from an invoice, how they move through a job, and how Mavri turns one into the other.

Raktim Ranjit ·