# Rebilly Webhooks

Rebilly sends a JSON webhook when something happens in your organization: a transaction is
processed, a customer is created, a subscription is activated, an invoice is issued. When the
webhook has HTTP basic access authentication credentials, every request carries
`Authorization: Basic base64(username:password)`, and your endpoint authenticates the request by
checking them. Rebilly also signs every webhook (see below), but Hookdeck's Rebilly source checks
only basic auth.

Hookdeck setup note for the Rebilly source: "Set HTTP basic access authentication on the Rebilly webhook. Its JWT and Ed25519 signatures are not checked."

## When to Use This Skill

* How do I receive Rebilly webhooks?
* How do I check the `Authorization: Basic` header on a Rebilly webhook?
* Does Rebilly sign webhooks? (Yes, `Security-Signature` JWT and `v1a` Ed25519, but the examples check basic auth)
* How do I handle `transaction-processed`, `subscription-activated` or `invoice-issued` events?
* Why does my Rebilly webhook endpoint return 401, or why does Rebilly keep retrying?

## Verification (core)

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

const BASIC_RE = /^basic +([A-Za-z0-9+/]+={0,2})$/i; // strict base64 alphabet, scheme case-insensitive
const digest = (b) => crypto.createHash('sha256').update(b).digest();
const safeEqual = (a, b) => crypto.timingSafeEqual(digest(a), digest(b));

function verifyRebillyRequest(headers, username, password) {
  if (!username || !password) return false;            // never accept when unconfigured
  const m = BASIC_RE.exec(headers.authorization || '');
  if (!m || m[1].length % 4 !== 0) return false;
  const decoded = Buffer.from(m[1], 'base64');
  if (decoded.toString('base64') !== m[1]) return false; // reject non-canonical base64
  const colon = decoded.indexOf(0x3a);                 // split on the FIRST colon
  if (colon === -1) return false;
  const userOk = safeEqual(decoded.subarray(0, colon), Buffer.from(username));
  const passOk = safeEqual(decoded.subarray(colon + 1), Buffer.from(password));
  return userOk && passOk;                             // both evaluated, no short-circuit
}

```

In Python, decode with `base64.b64decode(value, validate=True)`, split with `bytes.partition(b":")`
and compare with `hmac.compare_digest` on `bytes` (encode both sides; `str` raises on non-ASCII).

* Header: `Authorization: Basic base64(username:password)`, set on the webhook in Rebilly. Environment
  variables: `REBILLY_WEBHOOK_USERNAME` and `REBILLY_WEBHOOK_PASSWORD`.
* Missing or malformed header, or a mismatch: `401`. Check it first, for every HTTP method.
* Hookdeck never sends a 401 challenge (no `WWW-Authenticate` round trip), so the credentials must
  arrive with each request.
* **A static shared credential authenticates the sender, not the body, and gives no replay
  protection** (no signature, no timestamp). Use HTTPS and rotate the credential if it leaks.
* Read the body raw (`express.raw({ type: '*/*' })`) and parse it only after the check.

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

## Rebilly Also Signs Webhooks

Every Rebilly webhook carries a `Security-Signature` header (an RS256 JWT) and Standard Webhooks
`v1a` Ed25519 signatures in `webhook-id`, `webhook-timestamp` and `webhook-signature`. Hookdeck's
Rebilly source does not check them, and the examples don't either. Rebilly's 5-minute timestamp
check would reject Hookdeck retries, which resend the original timestamp. The optional `v1a` check
is described in [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/rebilly-webhooks/references/verification.md).

## Methods

A Rebilly webhook's method is one of GET, POST, PUT, PATCH or DELETE, so the handlers accept all
five, check the credentials before anything else and handle an empty body. Hookdeck's Rebilly
source pre-selects POST without managing the method, so any other method the webhook uses must
also be allowed on the source.

## Event Types

The payload has an `eventType` field plus the resource ids and HAL `_embedded` / `_links`. A
webhook's optional body template replaces this default payload. Dispatch on `eventType`.

| Event | Sent When |
| --- | --- |
| `transaction-processed` | A transaction is processed |
| `transaction-declined` | A transaction is declined |
| `customer-created` | A customer is created |
| `subscription-activated` | A subscription is activated |
| `subscription-canceled` | A subscription is canceled |
| `invoice-issued` | An invoice is issued |
| `invoice-paid` | An invoice is paid |
| `data-export-completed` | A data export is completed |

The full list is in the `eventsFilter` vocabulary of [Rebilly's API reference](https://www.rebilly.com/_spec/catalog/all.yaml).

## Responding

Rebilly retries unless the response is **exactly HTTP 200** (not 202 or 204), and also if there is
no response within 20 seconds. Return `200` quickly and process asynchronously. Webhooks can be
delivered more than once, so handle them idempotently.

## Environment Variables

```bash
REBILLY_WEBHOOK_USERNAME=your-webhook-username
REBILLY_WEBHOOK_PASSWORD=your-webhook-password

```

## Local Development

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

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

```

Use the printed URL as the webhook URL in Rebilly.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/rebilly-webhooks/references/overview.md) - What Rebilly webhooks are, events and payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/rebilly-webhooks/references/setup.md) - Create the webhook, set basic auth, configure the Hookdeck source
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/rebilly-webhooks/references/verification.md) - Basic auth check, gotchas, optional signature checks
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/rebilly-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests