Back to documentation

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

MethodPathScope
get/api/v1/customerscustomers:read
post/api/v1/customerscustomers:write
get/api/v1/customers/{id}customers:read
get/api/v1/jobsjobs:read
get/api/v1/jobs/{id}jobs:read
get/api/v1/invoicesinvoices: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