# PAY.JP Webhooks

PAY.JP (Japanese payments) sends a JSON webhook to your URL when something happens in your account: a charge succeeds or fails, a subscription renews, a transfer is confirmed. Webhooks are **not signed**. Every request is a `POST` that carries an `X-Payjp-Webhook-Token` header holding your account's webhook token (`whook_...`), and your endpoint authenticates the request by comparing that header with the token you have stored.

## When to Use This Skill

* How do I receive PAY.JP webhooks?
* How do I verify the `X-Payjp-Webhook-Token` header?
* Does PAY.JP sign webhook payloads? (No: use the shared token)
* How do I handle `charge.succeeded`, `charge.failed` or `subscription.renewed` events?
* Why does my PAY.JP webhook endpoint keep getting retried?

## Verification (core)

```javascript
const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyPayjpRequest(headers, secret = process.env.PAYJP_WEBHOOK_SECRET) {
  if (!secret) return false;                          // never accept when unconfigured
  const received = headers['x-payjp-webhook-token'];  // whole value, e.g. whook_a09d...
  return Boolean(received) && safeEqual(received, secret);
}

```

* Header: `X-Payjp-Webhook-Token: whook_...`, the token set by PAY.JP for your account (you don't choose it). Compare the **whole** value, case-insensitive header name.
* Hookdeck shows this setup note for the PAY.JP source: "Use the webhook token (whook_...) shown in your PAY.JP account settings."
* Return `401` on a missing or wrong token, `400` for a body that isn't a JSON object, and `200` once handled. PAY.JP requires HTTP 200 (the body is ignored); `4xx`/`5xx` responses are retried every 3 minutes, at most 3 times.
* Read the body raw (`express.raw({ type: '*/*' })`) so any content type works, authenticate first, and parse afterwards.
* **A static shared token authenticates the sender, not the body, and gives no replay protection** (no signature, no timestamp). Always receive webhooks over HTTPS and rotate the token if it leaks. Dedupe on the event `id`.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/payjp-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/payjp-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/payjp-webhooks/examples/fastapi/).

## Event Types

The body is a v1 event `{id, object: "event", livemode, type, created, data, pending_webhooks}`. Dispatch on `type`; `data` is the same object the API returns.

| Event | Sent When |
| --- | --- |
| `charge.succeeded` | A payment succeeds |
| `charge.failed` | A payment fails |
| `charge.updated` | A charge is updated |
| `charge.refunded` | A charge is refunded |
| `charge.captured` | An authorized charge is captured |
| `dispute.created` | A chargeback occurs |
| `customer.created` | A customer is created |
| `subscription.created` | A subscription is created |
| `subscription.renewed` | A subscription period renews |
| `subscription.canceled` | A subscription is canceled |
| `transfer.succeeded` | A transfer (payout) is confirmed |

See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/payjp-webhooks/references/overview.md) for the full list.

## Environment Variables

```bash
PAYJP_WEBHOOK_SECRET=whook_xxxxxxxxxxxxxxxxxxxxxxxxxx   # PAY.JP account settings (アカウント設定)

```

## Local Development

PAY.JP can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 payjp --path /webhooks/payjp

```

Use the printed URL as the webhook destination in the PAY.JP dashboard.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/payjp-webhooks/references/overview.md) - What PAY.JP webhooks are, events and payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/payjp-webhooks/references/setup.md) - Add the destination and find the webhook token
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/payjp-webhooks/references/verification.md) - Token check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/payjp-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests