# Receive and verify webhooks

Get signed, real-time notifications (registrations, renewals, transfers, upcoming expirations, DNS changes) instead of polling — and prove each delivery really came from Porkbun.

## 1. Register an endpoint

```bash
curl -X POST https://api.porkbun.com/api/json/v3/webhook/create \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","url":"https://example.com/porkbun/webhook","events":["*"]}'
```

The response includes a **`secret`** — store it securely. It's the HMAC key you'll verify deliveries with. Omit `events` (or pass `["*"]`) for all event types; you can also subscribe to specific ones or a prefix wildcard like `dns.*`. Call `GET /webhook/eventTypes` for the catalog.

## 2. Verify every delivery

Each POST to your endpoint carries these headers:

- `X-Porkbun-Event` — the event type
- `X-Porkbun-Webhook-Id` — the event UUID (dedupe on this; an event may arrive more than once)
- `X-Porkbun-Webhook-Timestamp` — Unix seconds when it was signed
- `X-Porkbun-Signature` — `sha256=` + the signature

The signature is `HMAC-SHA256(secret, "{timestamp}.{rawBody}")` over the **raw request body** (verify before parsing JSON). Compare with a constant-time check and reject stale timestamps to blunt replays:

```php
$ts   = $_SERVER['HTTP_X_PORKBUN_WEBHOOK_TIMESTAMP'];
$sig  = $_SERVER['HTTP_X_PORKBUN_SIGNATURE'];          // "sha256=..."
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, $endpointSecret);
if (!hash_equals($expected, $sig) || abs(time() - (int)$ts) > 300) { http_response_code(400); exit; }
// signature OK — now json_decode($body) and process
```

```javascript
import crypto from 'node:crypto';
const expected = 'sha256=' + crypto.createHmac('sha256', endpointSecret)
  .update(`${ts}.${rawBody}`).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))
  && Math.abs(Date.now()/1000 - Number(ts)) < 300;
```

Return any `2xx` to acknowledge. Non-2xx/timeout/connection errors are retried with exponential backoff (~1m, 5m, 30m, 2h, 6h, up to 6 attempts); 20 consecutive failures auto-disables the endpoint and emails you.

## 3. Test it end-to-end

```bash
curl -X POST https://api.porkbun.com/api/json/v3/webhook/test \
  -H 'Content-Type: application/json' \
  -d '{"apikey":"pk1_...","secretapikey":"sk1_...","id":42}'
```

This queues a `webhook.test` event so you can confirm your receiver and signature check work.

## 4. Inspect and replay

- `GET /webhook/deliveries` — recent delivery log (filter by `endpointId`/`status`), ~30-day history
- `GET /webhook/delivery/{id}` — one delivery with its full payload
- `POST /webhook/resend` `{id}` — re-queue a past delivery after you fix a bug (reuses the original event id, so your dedupe treats it as the same event)

## Related

- Webhook endpoints: https://porkbun.com/llms/webhooks
- [Getting started](https://porkbun.com/llms/guides/getting-started)


---

## More

- Guides (how-tos): https://porkbun.com/llms/guides
- Topic index: https://porkbun.com/llms
- Full reference (one file): https://porkbun.com/llms-full.txt
- OpenAPI spec (full schemas): https://porkbun.com/api/json/v3/spec
- Short overview: https://porkbun.com/llms.txt
- Official MCP server: https://github.com/oborseth/Porkbun-MCP (`npx -y @porkbunllc/mcp-server`)
- Create API keys: https://porkbun.com/account/api
