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.update or product.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 from TRIPLETEX_WEBHOOK_HEADER, default Authorization). Look it up case-insensitively.
  • Compare the whole value with TRIPLETEX_WEBHOOK_SECRET, exactly as you typed it as authHeaderValue. If that was Bearer abc123, the secret is Bearer abc123. Do not strip or parse the Bearer prefix.
  • 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_digest on bytes (encode both sides, or compare SHA-256 digests); passing str raises TypeError on non-ASCII input.
  • Basic credentials in the targetUrl and ?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.

EventSent When
invoice.chargedA new invoice has been created and charged
customer.create / .update / .deleteA customer is created / updated / deleted
order.create / .update / .deleteAn order is created / updated / deleted
product.create / .update / .deleteA product is created / updated / deleted
supplier.*, employee.*, account.*, project.*, contact.*, archiverelation.*, voucher.*Same create / update / delete verbs
notification.sentA notification was sent to the employee (token owner)
voucherstatus.readyA new voucher is ready to be processed from the voucher reception
closegroup.createA close group is created (postings matched/closed), incl. automatic closes
closegroup.deleteA 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 9, 2026

View on GitHub →