Agent skill
Tripletex Webhooks Skill
Receive and authenticate Tripletex webhooks (event subscriptions). Use when setting up a Tripletex webhook endpoint, checking the authHeaderName / authHeaderValue header, debugging 401s, or handling accounting events like invoice.charged, customer.create, order.update and product.delete.
Install this skill
npx skills add hookdeck/webhook-skills --skill tripletex-webhooks
Tripletex (Norwegian accounting) sends a JSON webhook when an object you subscribed to changes: an invoice is charged, a customer is created, an order is updated, a product is deleted. Subscriptions are created through the API (POST /v2/event/subscription); there is no dashboard UI for it. Webhooks are not signed. The only built-in authentication is a custom header you choose when creating the subscription (authHeaderName / authHeaderValue), which Tripletex sends as-is on every callback.
When to Use This Skill
- How do I receive Tripletex webhooks?
- How do I authenticate Tripletex event subscription callbacks?
- Does Tripletex sign webhook payloads? (No: use
authHeaderName/authHeaderValue) - How do I handle
invoice.charged,customer.create,order.updateorproduct.delete? - Why does my Tripletex webhook endpoint return 401?
Verification (core)
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyTripletexRequest(
headers,
secret = process.env.TRIPLETEX_WEBHOOK_SECRET,
headerName = process.env.TRIPLETEX_WEBHOOK_HEADER || 'Authorization'
) {
if (!secret) return false; // never accept when unconfigured
const received = headers[headerName.toLowerCase()]; // Node lowercases header names
return Boolean(received) && safeEqual(received, secret); // whole value, no Bearer parsing
}
- The header name is whatever you set as
authHeaderName(the examples read it fromTRIPLETEX_WEBHOOK_HEADER, defaultAuthorization). Look it up case-insensitively. - Compare the whole value with
TRIPLETEX_WEBHOOK_SECRET, exactly as you typed it asauthHeaderValue. If that wasBearer abc123, the secret isBearer abc123. Do not strip or parse theBearerprefix. - Missing header or mismatch:
401. Malformed body (after authenticating):400. Handled:200. - Read the body raw (
express.raw({ type: '*/*' })) and authenticate before parsing it. - A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS and rotate the credential if it leaks.
- Python:
hmac.compare_digeston bytes (encode both sides, or compare SHA-256 digests); passingstrraisesTypeErroron non-ASCII input. - Basic credentials in the
targetUrland?token=query parameters are also supported by Tripletex, but they aren't covered by this skill; prefer the custom header.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Types
Events are named object.verb. The body is { subscriptionId, event, id, value }; value is the object DTO (shaped by the subscription's optional fields), or null for deletes.
| Event | Sent When |
|---|---|
invoice.charged | A new invoice has been created and charged |
customer.create / .update / .delete | A customer is created / updated / deleted |
order.create / .update / .delete | An order is created / updated / deleted |
product.create / .update / .delete | A product is created / updated / deleted |
supplier.*, employee.*, account.*, project.*, contact.*, archiverelation.*, voucher.* | Same create / update / delete verbs |
notification.sent | A notification was sent to the employee (token owner) |
voucherstatus.ready | A new voucher is ready to be processed from the voucher reception |
closegroup.create | A close group is created (postings matched/closed), incl. automatic closes |
closegroup.delete | A close group is deleted (postings reopened); value is null |
You must subscribe to each verb separately. GET https://tripletex.no/v2/event lists the events currently available.
Environment Variables
TRIPLETEX_WEBHOOK_HEADER=Authorization # your authHeaderName
TRIPLETEX_WEBHOOK_SECRET=Bearer your-long-random-secret # your authHeaderValue, exactly
Local Development
Tripletex can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 tripletex --path /webhooks/tripletex
Use the printed URL as the targetUrl of the event subscription.
Resources
- references/overview.md - What Tripletex webhooks are, events and payload shape
- references/setup.md - Create an event subscription with an auth header
- references/verification.md - Header check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests