# HitPay Webhooks

HitPay is a payments platform (Singapore and Southeast Asia). Its **event webhooks**, registered under Developers > Webhooks, are HTTP POSTs sent after events such as a completed payment, a new order or a transfer. Each request carries `Hitpay-Signature`: the hex HMAC-SHA256 of the **raw JSON body**, keyed with that webhook endpoint's **salt**.

**Not covered:** the legacy payment-request and charge callbacks (form-encoded, with an `hmac` field in the payload, keyed with the API-key salt from the main Developers page). They use a different scheme: sorted, concatenated parameters rather than the raw body.

## When to Use This Skill

* How do I receive HitPay webhooks?
* How do I verify the `Hitpay-Signature` header?
* Which salt do I use: the API-key salt or the webhook salt?
* How do I tell `charge.created` from `order.created`? (The body has no event name.)
* Why is my HitPay webhook signature verification failing?

## Verification (core)

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

function verifyHitpaySignature(rawBody, signature, salt = process.env.HITPAY_WEBHOOK_SALT) {
  if (!salt) return false;                                    // never accept when unconfigured
  // Check shape first: Buffer.from(x, 'hex') silently truncates invalid hex.
  if (typeof signature !== 'string' || !/^[0-9a-f]{64}$/i.test(signature)) return false;
  const expected = crypto.createHmac('sha256', salt).update(rawBody).digest('hex');
  // Equal-length (64 char) buffers, so timingSafeEqual cannot throw.
  return crypto.timingSafeEqual(Buffer.from(signature.toLowerCase()), Buffer.from(expected));
}

```

Python: `hmac.compare_digest(expected.encode(), signature.lower().encode())` on **bytes**, so a non-ASCII header gives `401` rather than a `TypeError` and a 500. Use the raw body bytes, never re-serialised JSON. Express: `express.raw({ type: '*/*' })` so any content type verifies. Details in [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/hitpay-webhooks/references/verification.md).

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

## Event Types

The body is the object itself and carries no event name. The event arrives in two headers: `Hitpay-Event-Object` (`charge`, `payout`, `invoice`, `order`, `transfer`) and `Hitpay-Event-Type` (`created`, `updated`, ...). Dispatch on `{Hitpay-Event-Object}.{Hitpay-Event-Type}`.

| Event | Triggered When |
| --- | --- |
| `charge.created` | A payment is successfully completed |
| `charge.updated` | A payment is refunded or partially refunded |
| `payout.created` | A payout is successfully completed |
| `order.created` | An order is created |
| `order.updated` | An order status is updated |
| `invoice.created` | An invoice is created |
| `transfer.created`, `transfer.updated` | A transfer is created or updated |
| `transfer.processing`, `transfer.scheduled` | A transfer is processed or scheduled |
| `transfer.paid`, `transfer.failed`, `transfer.canceled` | A transfer is paid, fails or is canceled |

HitPay's list also names `payment_request.*` and `recurring_billing.*` events; see [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/hitpay-webhooks/references/overview.md).

## Environment Variables

```bash
HITPAY_WEBHOOK_SALT=your_webhook_salt_from_dashboard   # Developers > Webhooks > the webhook's detail view

```

## Setup

Register the endpoint under **Developers > Webhooks** in the HitPay dashboard and copy the **salt** from that webhook's detail view (not the API-key salt on the main Developers page). Details in [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/hitpay-webhooks/references/setup.md).

**With Hookdeck:** only HitPay App webhooks (Developers > Webhooks), which send a `Hitpay-Signature` header, are supported. Paste that webhook's salt as the source's webhook secret. Legacy payment-request callbacks aren't supported.

## Local Development

For local webhook testing, run the Hookdeck CLI via `npx` (no account required):

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

```

Paste the printed URL into HitPay Dashboard > Developers > Webhooks.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/hitpay-webhooks/references/overview.md) - What HitPay webhooks are, event types, headers, payloads
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/hitpay-webhooks/references/setup.md) - Register a webhook, find the salt, use Hookdeck
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/hitpay-webhooks/references/verification.md) - Signature details, the two salts, gotchas, debugging
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/hitpay-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests