---
title: "Create a webhook endpoint"
method: POST
path: "/companies/{company}/webhook-endpoints"
tags: ["Webhooks"]
---

# Create a webhook endpoint

`POST /companies/{company}/webhook-endpoints`

## Request

- Base URL: `https://api.factuarea.com/v1`
- URL: `https://api.factuarea.com/v1/companies/{company}/webhook-endpoints`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

Create a webhook endpoint that receives event notifications via HTTPS callbacks. The signing `secret` is returned **once** in this response and never again — store it securely.

## Path parameters

- `company` string, uuid, required

## Headers

- `Idempotency-Key` string
- `Factuarea-Version` string, date

## Request body

- CreateWebhookEndpointRequest
  - `url` string, required
  - `enabled_events` string[], required
  - `description` string, nullable
  - `ip_allowlist` string[], nullable
  - `api_version` string, nullable: Payload version (date-based, `YYYY-MM-DD`) to pin for the events delivered to this endpoint. Supported values: `2026-05-22`, `2026-10-01` and `2026-10-05`. Omitted or `null`, the endpoint is pinned at creation to the payload version that matches the effective REST version of the request: `2026-10-05` (the latest) when that version is `2026-10-01` or later (`Factuarea-Version` header, or the version pinned on the API key), and `2026-05-22` otherwise, including requests without a version header. So nobody receives the new vocabulary without opting in. With `2026-10-01` amounts are JSON numbers and the payment keys are `paid_at`/`paid_on`; with `2026-10-05` amounts are decimal strings and the payment keys are `paid_recorded_at`/`paid_date`. With `2026-05-22`, invoices keep the previous vocabulary (`status: sent` for an issued invoice, `sent_at` equal to the issuance instant). A value that is not a `YYYY-MM-DD` date returns 422 (subcode `api_version_invalid_format`), and an unsupported one 422 (subcode `api_version_unsupported`).
  - `metadata` Metadata, nullable: A free map of up to 50 key→value pairs for storing arbitrary structured data (values are strings up to 500 characters). Unlike `custom_fields` — an ordered list of typed `{field, value}` pairs with display semantics, present on the six document resources — `metadata` is an unordered map for opaque integration data; a document may carry both. The master resources (Client, Supplier) have no `custom_fields`, so their `metadata` doubles as the custom-fields store. **Reserved keys (read-only).** When the system auto-issues an invoice from a payment correlation (Stripe/GoCardless/MONEI), it writes `stripe_subscription_id`, `stripe_invoice_id`, `billing_reason`, `period_start` and `period_end` into that invoice metadata automatically. Do not set or overwrite them by hand — the platform owns them and a manual value may be replaced when the correlation runs.
  - `custom_headers` CustomHeaders: Custom HTTP headers (string → string) added to every delivery request for a webhook endpoint. Max 20 entries, each value ≤ 1024 chars. Header names are normalized to Title-Case. Reserved names (`Host`, `Content-Type`, `Factuarea-Signature`, `X-Forwarded-*`, …) are rejected. Empty object when none are set. Never put secrets here — values are returned verbatim by the API.
  - `timeout_seconds` integer, nullable

## Response `201`

Webhook endpoint created successfully. The `Location` header contains the canonical URL of the newly created resource.

- object
  - `data` WebhookEndpointWithSecret, required: A WebhookEndpoint returned once at creation or after secret rotation. Includes the plain-text signing secret.
    - `id` string, required
    - `object` 'webhook_endpoint', required
    - `url` string, uri, required: HTTPS callback URL.
    - `description` string, nullable, required
    - `enabled_events` string[], required: List of event types this endpoint subscribes to. `invoice.sent` is a deprecated alias of `invoice.issued` (same instant, same `data.object`): it is still accepted so existing endpoints keep working, but new integrations should subscribe to `invoice.issued`.
    - `status` string, required: Status of the endpoint: `active` (it receives deliveries), `degraded` (every delivery of the last 24 hours failed: deliveries are paused until a `ping` succeeds) or `disabled` (turned off by you: it receives none).
    - `ip_allowlist` string[], nullable, required: List of allowed source IPs.
    - `delivery_success_rate_24h` number, nullable, required
    - `last_delivery_at` string, date-time, nullable, required
    - `last_failure_at` string, date-time, nullable, required
    - `previous_secret_valid_until` string, date-time, nullable, required
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
    - `api_version` string, nullable, required: Payload version (date-based, e.g. `2026-10-01`) pinned for the events delivered to this endpoint. An endpoint created without an explicit `api_version` is pinned at creation (`2026-10-01` when the effective REST version of the request is `2026-10-01` or later, `2026-05-22` otherwise). `null` only remains if it was cleared explicitly, and then the payload version of the default REST version (currently `2026-05-22`) applies, never the latest one. An endpoint pinned to a payload version before `2026-10-01` receives invoices in the previous vocabulary: `status: sent` for an issued invoice, `sent_at` equal to the issuance instant, and no `issued_at`, `is_sent` or `sent_via`.
    - `metadata` Metadata, nullable, required: A free map of up to 50 key→value pairs for storing arbitrary structured data (values are strings up to 500 characters). Unlike `custom_fields` — an ordered list of typed `{field, value}` pairs with display semantics, present on the six document resources — `metadata` is an unordered map for opaque integration data; a document may carry both. The master resources (Client, Supplier) have no `custom_fields`, so their `metadata` doubles as the custom-fields store. **Reserved keys (read-only).** When the system auto-issues an invoice from a payment correlation (Stripe/GoCardless/MONEI), it writes `stripe_subscription_id`, `stripe_invoice_id`, `billing_reason`, `period_start` and `period_end` into that invoice metadata automatically. Do not set or overwrite them by hand — the platform owns them and a manual value may be replaced when the correlation runs.
    - `custom_headers` CustomHeaders, required: Custom HTTP headers (string → string) added to every delivery request for a webhook endpoint. Max 20 entries, each value ≤ 1024 chars. Header names are normalized to Title-Case. Reserved names (`Host`, `Content-Type`, `Factuarea-Signature`, `X-Forwarded-*`, …) are rejected. Empty object when none are set. Never put secrets here — values are returned verbatim by the API.
    - `timeout_seconds` integer, nullable, required: HTTP request timeout in seconds applied when delivering to this endpoint (1–30). `null` means the system default applies.
    - `degraded_since` string, date-time, nullable, required: Timestamp since which the endpoint is considered degraded (sustained delivery failures). `null` when healthy.
    - `secret` string, nullable, required: Signing secret, returned only at creation or after rotation. `null` only in an idempotent replay of that creation or rotation, because the secret is never stored.

## Other responses

- `400`: `parameter_unknown` with `subcode` `retired_active_profile_header`: the retired `X-Active-Profile` header was sent; the company goes in the path. `parameter_invalid_format` on `Factuarea-Version`: the header is not a `YYYY-MM-DD` date. `unsupported_api_version`: the `Factuarea-Version` date is not a supported version.
- `401`: Missing or invalid API key.
- `402`: The operation requires a payment that could not be completed: either no payment method is on file (`error.details.payment_setup_url` links to the Billing Portal where it can be set up), the immediate charge was declined by the payment provider, the account lacks the plan or add-on this operation bills against, the annual document limit is reached (`plan_limit_exceeded`, with `error.details.period_end` indicating the end of the period — wait until it has ended or upgrade the plan to continue now), or the account has reached its maximum usage (`storage_quota_exceeded`, raised by upload operations such as signing a delivery note or attaching a file to an expense — contact support before retrying). Nothing was created or modified — resolve the cause and retry the same request. Version note: `error.type` is `payment_required_error` from `Factuarea-Version: 2026-09-01` onwards; earlier versions receive `invalid_request_error` for the three codes that predate that cut (`payment_method_required`, `employee_seat_payment_method_required` and `employee_seat_charge_failed`; every other 402 code, such as `addon_required`, `plan_limit_exceeded` or the additional tax ID codes, is newer and always carries `payment_required_error`). `error.code` is stable across every version.
- `403`: The request is authenticated but not allowed: the API key lacks the required scope (`insufficient_scope`), the plan does not include the public API or this feature (`addon_not_active`, `feature_not_available_in_plan`), or the module is not available with a test key (`module_not_available_in_sandbox`).
- `404`: The requested resource does not exist or belongs to another company. `profile_not_found`: the company in the path does not exist, is not in the API key scope or belongs to the other environment (live or test).
- `422`: Validation failed. The `error.param` field identifies which input is invalid.
- `429`: Rate limit exceeded. Retry after the duration in `Retry-After`.
- `500`: Unexpected server error.

---

[API](https://skmtc.dev/factuarea/apis/factuarea-public-api.md) · [All operations](https://skmtc.dev/factuarea/apis/factuarea-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/factuarea/apis/factuarea-public-api/revisions/718e77934422?raw)
