Public API and webhooks
This guide describes the currently available public v1 API and the complete webhook contract.
API keys and scopes
Send the key in the X-API-Key header. Authorization: Bearer <key> is also accepted. Every key is limited to one company and its selected scopes.
The complete API key is displayed only once when it is created. Store it securely immediately; if it is lost, create a new key.
curl https://manoplan.de/api/v1/customers \
--header "X-API-Key: mp_your_key"Available scopes
- customers:read
- customers:write
- jobs:read
- invoices:read
Available endpoints
| Method | Path | Scope |
|---|---|---|
| get | /api/v1/customers | customers:read |
| post | /api/v1/customers | customers:write |
| get | /api/v1/customers/{id} | customers:read |
| get | /api/v1/jobs | jobs:read |
| get | /api/v1/jobs/{id} | jobs:read |
| get | /api/v1/invoices | invoices:read |
| get | /api/v1/invoices/{id} | invoices:read |
Webhook contract
ManoPlan sends JSON events to active HTTPS endpoints. The event name is also provided in X-Manoplan-Event.
The webhook secret is displayed only once when the endpoint is created. Treat it like a password and replace the endpoint if it is lost.
Events
- customer.created
- job.created
- job.status_changed
- invoice.created
Payload envelope
{
"id": "delivery-id",
"event": "job.status_changed",
"createdAt": "2026-08-27T12:00:00.000Z",
"data": {}
}Delivery headers
- X-Manoplan-Event
- X-Manoplan-Delivery
- X-Manoplan-Signature
Verify signatures securely
Calculate HMAC-SHA256 over the unchanged raw body before parsing JSON. Compare the bytes in constant time with X-Manoplan-Signature. Reject requests with a missing or invalid signature.
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyWebhookSignature(rawBody, signature, secret) {
if (!signature.startsWith('sha256=')) return false;
const digest = signature.slice('sha256='.length);
if (!/^[0-9a-f]{64}$/i.test(digest)) return false;
const expected = createHmac('sha256', secret).update(rawBody).digest();
const received = Buffer.from(digest, 'hex');
return timingSafeEqual(expected, received);
}Delivery, retries, and deduplication
Delivery is at least once. Respond quickly with 2xx and process each X-Manoplan-Delivery ID only once. ManoPlan retries failed deliveries on the following schedule.
Retries: 1m → 5m → 30m → 2h → 6h