Skip to main content

Webhooks

Webhooks let your application receive real-time HTTP callbacks when events occur in your Gäld organisation. When an event fires, Gäld sends a POST request to each registered webhook URL.

Setting Up a Webhook

Create a webhook via the API:

curl -X POST https://app.gaeld.ch/api/v1/webhooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/gaeld",
"events": ["invoice.created", "invoice.finalized", "customer.created"],
"is_active": true
}'

Response includes a secretstore it securely, you will need it to verify signatures.

Events

EventFires when
invoice.createdA new invoice is created
invoice.updatedAn invoice is modified
invoice.deletedAn invoice is deleted
invoice.finalizedAn invoice is finalized (locked for editing)
invoice.payment_recordedA payment is recorded against an invoice
customer.createdA new customer is created
customer.updatedA customer is modified
customer.deletedA customer is deleted
expense.createdA new expense is created
expense.updatedAn expense is modified
expense.deletedAn expense is deleted
expense.approvedAn expense is approved in the workflow

Use GET /api/v1/meta/webhook-events to retrieve this list programmatically.

Payload Format

Every webhook delivery sends a JSON POST request:

{
"event": "invoice.finalized",
"timestamp": "2025-06-01T12:00:00Z",
"data": {
"id": "uuid",
"number": "INV-2025-042",
"status": "finalized",
"customer": { "id": "uuid", "name": "ACME GmbH" },
"total": "1081.00",
"currency": "CHF"
}
}

The data field contains the full resource representation (same format as the corresponding API endpoint response).

Request Headers

Each webhook request includes these headers:

HeaderDescription
Content-Typeapplication/json
X-Webhook-SignatureHMAC-SHA256 hex digest of the request body
X-Webhook-EventEvent name (e.g. invoice.created)
X-Webhook-IdUnique delivery ID
User-AgentGaeld-Webhook/1.0

Signature Verification

Every webhook request is signed using HMAC-SHA256 with the webhook's secret. Always verify the signature before processing the payload.

PHP

$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';

$computed = hash_hmac('sha256', $payload, $webhookSecret);

if (! hash_equals($computed, $signature)) {
http_response_code(400);
exit('Invalid signature');
}

$event = json_decode($payload, true);
// Process $event

Node.js

const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
const computed = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(computed),
Buffer.from(signature)
);
}

// In your Express handler:
app.post('/webhooks/gaeld', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'];
if (!verifySignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(400).send('Invalid signature');
}
const event = JSON.parse(req.body);
// Process event
res.sendStatus(200);
});

Python

import hmac
import hashlib

def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
computed = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(computed, signature)

Delivery & Retries

PropertyValue
HTTP timeout10 seconds
Max attempts3
Retry backoff30 seconds, 5 minutes, 1 hour
Expected responseAny 2xx status code

If your endpoint returns a non-2xx status or times out, Gäld retries with exponential backoff. After 3 failed attempts the delivery is marked as failed. You can check delivery status via the webhook API.

Managing Webhooks

ActionMethodEndpoint
List allGET/api/v1/webhooks
View detailsGET/api/v1/webhooks/{id}
CreatePOST/api/v1/webhooks
Update events/URLPUT/api/v1/webhooks/{id}
DeleteDELETE/api/v1/webhooks/{id}
Regenerate secretPOST/api/v1/webhooks/{id}/regenerate-secret
Secret rotation

After calling regenerate-secret, update your application with the new secret immediately. The old secret is invalidated and all subsequent deliveries will use the new one.

Best Practices

  • Always verify signatures — never trust payload content without checking the HMAC signature
  • Respond quickly — return 200 immediately and process the event asynchronously (e.g. via a job queue)
  • Handle duplicates — use the X-Webhook-Id header to deduplicate if your endpoint is called more than once
  • Use HTTPS — webhook URLs must use HTTPS in production
  • Monitor failures — check webhook delivery status periodically and fix failing endpoints