Arubis webhooks
Arubis sends a signed HTTPS request to your endpoint when a document, contact or lead changes. Events are queued and sent in batches, so expect each one within about 15 minutes of the change.
Add an endpoint#
- In Arubis, open Settings, API and webhooks and choose Add endpoint.
- Enter an
https://URL that resolves to a public address, and choose the events to receive. - Copy the signing secret. It starts with
whsec_and is shown once.
Use Send test to queue a test.ping event straight away. The endpoint list shows the last delivery, its HTTP status and recent attempts.
Events#
| Event | Sent when |
|---|---|
invoice.created | An invoice was created, in Arubis or through the API |
invoice.issued | An invoice was issued and posted to the books |
invoice.paid | An invoice was paid in full |
invoice.voided | An invoice was voided |
quote.accepted | A customer accepted a quote |
quote.declined | A customer declined a quote |
bill.created | A bill was entered |
bill.approved | A bill was approved |
bill.paid | A bill was paid in full |
contact.created | A customer or supplier was added |
lead.won | A lead was marked won |
test.ping | You chose Send test |
The request#
POST /arubis/webhooks HTTP/1.1
Content-Type: application/json
User-Agent: Arubis-Webhooks/1
X-Arubis-Event: invoice.paid
X-Arubis-Delivery: 0c9b3c4e-6a55-4d0e-8a4f-1f3f2b7d9e10
X-Arubis-Signature: t=1792040000,v1=9f2c...b71a{
"id": "5e1d2c3b-4a59-4f68-8e7d-6c5b4a392817",
"type": "invoice.paid",
"created_at": "2026-10-15T09:12:44.103Z",
"org_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"data": {
"id": "7a0c5c1e-2f61-4c0e-a1f0-0d5c9d1b2e33",
"kind": "invoice",
"number": "INV-0042",
"status": "paid",
"contact_id": "3d6f8f8a-0b8e-4f5e-9c2b-6c7a0e0d41aa",
"date": "2026-10-01",
"due_date": "2026-10-31",
"currency": "BWP",
"subtotal": 900.00,
"tax_total": 126.00,
"total": 1026.00,
"amount_paid": 1026.00,
"reference": "Order 1001"
}
}Document events carry the fields shown above. contact.created carries id, name, email, is_customer, is_supplier and currency. lead.won carries id, name, company, value, currency, contact_id and quote_id.
The payload is a summary. Fetch the full record from the API when you need the lines.
Verify the signature#
X-Arubis-Signature holds a Unix timestamp t and v1, the hex HMAC-SHA256 of <t>.<raw body> keyed with your signing secret.
- Read the raw request body before parsing it.
- Compute the HMAC over
t + "." + bodyand compare it tov1in constant time. - Reject the request if
tis more than five minutes from your clock.
import crypto from 'node:crypto'
export function verifyArubis(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex')
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300
return fresh && expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
}
import hashlib, hmac, time
def verify_arubis(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
signed = parts["t"].encode() + b"." + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
fresh = abs(time.time() - int(parts["t"])) < 300
return fresh and hmac.compare_digest(expected, parts["v1"])Respond, retries and duplicates#
- Answer with any
2xxwithin 5 seconds. Do the work afterwards, for example on a queue. - Any other answer, or no answer, is a failure. Arubis retries with growing gaps, up to 8 attempts, then marks the delivery failed.
- After 20 failures in a row the endpoint is switched off. Switch it back on in Settings once it is fixed.
- A delivery can arrive more than once. Use
X-Arubis-Deliveryor the payloadidto skip ones you have already handled. - Events can arrive out of order. Compare
created_at, or fetch the record, before acting.