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

# Webhooks

> Verify event signatures and process events without duplicate effects.

## Configure an endpoint

Open Developers → Webhooks in the company console. Choose the correct environment and register a public HTTPS URL. The signing secret is shown once; store it in your server's secret manager. A webhook secret is different from an API key.

Private network addresses, local hosts, IP-literal URLs, and redirects are not supported. Your endpoint must respond within five seconds.

## Event envelope

```json theme={null}
{
  "id": "EVENT_ID",
  "type": "operation.completed",
  "environment": "test",
  "createdAt": "2026-09-20T12:00:00.000Z",
  "data": {"operationId":"OPERATION_ID","status":"completed","cardId":"CARD_ID"}
}
```

Possible operation events include `operation.queued`, `operation.provider_pending`, `operation.manual_review`, `operation.completed`, and `operation.failed`. Balance credits emit `balance.credited`. Event data depends on the event type; not every event contains a card ID.

## Verify the signature

The `Plane-Signature` header has the format `t=UNIX_SECONDS,v1=HEX_SIGNATURE`. Compute HMAC-SHA256 using your signing secret over `timestamp + "." + raw_request_body`. Verify against the original bytes, before JSON parsing or reserialization.

```javascript theme={null}
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyPlaneWebhook(rawBody, header, secret, now = Date.now()) {
  const match = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(header || '');
  if (!match) return false;
  const [, timestamp, received] = match;
  if (Math.abs(Math.floor(now / 1000) - Number(timestamp)) > 300) return false;
  const expected = createHmac('sha256', secret)
    .update(timestamp + '.').update(rawBody).digest();
  return timingSafeEqual(expected, Buffer.from(received, 'hex'));
}
```

The five-minute tolerance in this example is a receiver replay policy. Keep your server clock synchronized.

## Acknowledge and deduplicate

After verification, insert the event into a durable inbox with a unique constraint on `id`. Commit that insert before returning a 2xx response. Process business effects asynchronously. For a valid duplicate, return 2xx without repeating the effect.

Any 2xx response acknowledges delivery. Failed requests are retried up to eight total attempts, with exponential delays beginning at 15 seconds. Do not depend on exact retry times or event ordering. A recovered worker or lost acknowledgement may deliver the same event again. Fetch the operation's current status when an older event arrives.

The console displays delivery status and attempt counts. Exhausted events require investigation; polling the operation remains available.
