Agent skill
Rebilly Webhooks Skill
Receive and authenticate Rebilly subscription billing webhooks. Use when setting up a Rebilly webhook endpoint, checking HTTP basic access authentication, debugging 401s, accepting GET/POST/PUT/PATCH/DELETE webhooks, or handling events like transaction-processed, customer-created, subscription-activated, invoice-issued and data-export-completed.
Install this skill
npx skills add hookdeck/webhook-skills --skill 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: Basicheader on a Rebilly webhook? - Does Rebilly sign webhooks? (Yes,
Security-SignatureJWT andv1aEd25519, but the examples check basic auth) - How do I handle
transaction-processed,subscription-activatedorinvoice-issuedevents? - Why does my Rebilly webhook endpoint return 401, or why does Rebilly keep retrying?
Verification (core)
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_USERNAMEandREBILLY_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-Authenticateround 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/, examples/nextjs/, 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.
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.
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
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):
npx hookdeck-cli listen 3000 rebilly --path /webhooks/rebilly
Use the printed URL as the webhook URL in Rebilly.
Resources
- references/overview.md - What Rebilly webhooks are, events and payload shape
- references/setup.md - Create the webhook, set basic auth, configure the Hookdeck source
- references/verification.md - Basic auth check, gotchas, optional signature checks
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests