> ## Documentation Index
> Fetch the complete documentation index at: https://www.billingserv.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a webhook endpoint

> Accept, acknowledge, and safely process BillingServ webhooks.

A webhook endpoint is a public HTTPS URL in your application that accepts BillingServ JSON `POST` requests.

## Recommended flow

1. Accept `POST` requests over HTTPS.
2. Limit the maximum request size.
3. Parse the JSON envelope.
4. Require `id`, `type`, `created_at`, and `data`.
5. Confirm `Webhook-Id` and `Webhook-Event` match the payload.
6. Deduplicate using the event `id`.
7. Persist the payload or place it on a durable queue.
8. Return a `2xx` response quickly.
9. Process the business action asynchronously.

## Important security note

`Webhook-Id` and `Webhook-Event` are metadata, not cryptographic signatures. They help with routing and duplicate detection but do not prove who sent the request.

Use a long, unguessable endpoint path, keep it private, require HTTPS, and apply normal request-size and rate limits. Do not place credentials in the URL's username or password section.

Example endpoint:

```text theme={null}
https://example.com/webhooks/billingserv/8c449ac7bfc449b5a6e17dc1f257b720
```

## Standalone PHP example

This minimal PHP 8 endpoint validates and stores each delivery before acknowledging it. Keep the storage directory outside the public web root.

```php theme={null}
<?php

declare(strict_types=1);

const MAX_BODY_BYTES = 1_048_576;
const EVENT_LOG = '/var/lib/example/billingserv-webhooks.ndjson';

if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
    header('Allow: POST');
    http_response_code(405);
    exit;
}

$rawBody = file_get_contents('php://input');

if ($rawBody === false || strlen($rawBody) > MAX_BODY_BYTES) {
    http_response_code(413);
    exit;
}

try {
    $payload = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException) {
    http_response_code(400);
    exit;
}

$eventId = $_SERVER['HTTP_WEBHOOK_ID'] ?? '';
$eventType = $_SERVER['HTTP_WEBHOOK_EVENT'] ?? '';

if (! is_array($payload)
    || ! isset($payload['id'], $payload['type'], $payload['created_at'], $payload['data'])
    || ! is_array($payload['data'])
    || ! hash_equals((string) $payload['id'], $eventId)
    || ! hash_equals((string) $payload['type'], $eventType)) {
    http_response_code(400);
    exit;
}

$record = json_encode([
    'received_at' => gmdate(DATE_ATOM),
    'event_id' => $eventId,
    'event_type' => $eventType,
    'payload' => $payload,
], JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);

if (file_put_contents(EVENT_LOG, $record.PHP_EOL, FILE_APPEND | LOCK_EX) === false) {
    http_response_code(500);
    exit;
}

http_response_code(204);
```

For production, store `event_id` in a database column with a unique index and enqueue the payload in the same database transaction. The file example demonstrates receipt only; it does not provide durable deduplication across concurrent workers.

## Route events by type

Process only event types your integration understands and safely ignore unknown types:

```php theme={null}
match ($payload['type']) {
    'payment.succeeded' => handleSuccessfulPayment($payload['data']),
    'invoice.paid' => markInvoicePaid($payload['data']),
    'order.updated' => synchronizeOrder($payload['data']),
    default => null,
};
```

Do not reject an otherwise valid delivery only because its event type is new. Returning a failure for unknown types causes unnecessary retries.

## Test the endpoint

Save the URL in **Admin → API Information**, then select **Send test webhook**. A successful request has the type `webhook.test`:

```json theme={null}
{
  "id": "42fa1159-f498-4b90-bb89-437d8cf8f52f",
  "type": "webhook.test",
  "created_at": "2026-07-16T09:55:37.123124Z",
  "data": {
    "message": "This is a test webhook from BillingServ."
  }
}
```

Once the test succeeds, create or update a QA resource and confirm the corresponding event from the [event reference](/api-reference/webhooks/events).
