# Basiq Webhooks

[Basiq](https://basiq.io) is an Australian open-banking (CDR) data API covering users, consents, connections, accounts and transactions. Its webhooks are **thin notifications**: they say what happened and link to the event and the affected object, and you fetch the data from the Basiq API. Deliveries follow the Standard Webhooks scheme with `webhook-id`, `webhook-timestamp` and `webhook-signature` headers.

## When to Use This Skill

* How do I receive Basiq webhooks?
* How do I verify a Basiq webhook signature?
* Why is my Basiq `webhook-signature` verification failing?
* How do I create a Basiq webhook subscription (`POST /notifications/webhooks`)?
* How do I handle `user.created`, `connection.activated`, `consent.revoked` or `transactions.updated` events?
* Why doesn't the Basiq webhook payload contain the account or transaction data?

## Verification (core)

| Header | Value |
| --- | --- |
| `webhook-id` | Unique message ID, the same if the message is resent |
| `webhook-timestamp` | Unix seconds of the attempt |
| `webhook-signature` | One or more space-separated `v1,<base64>` entries |

Signed content is `{webhook-id}.{webhook-timestamp}.{raw body}`, HMAC-SHA256, base64 output. The key is the base64-**decoded** part of the endpoint's secret after `whsec_`. Each endpoint has its own secret. Basiq has no webhook SDK; the examples use `standardwebhooks` (npm, PyPI), which compares in constant time and tries every `v1` entry.

```javascript
const { Webhook } = require('standardwebhooks');

// rawBody: exact request body string, NOT re-serialised JSON
function verifyBasiq(rawBody, h, secret = process.env.BASIQ_WEBHOOK_SECRET) {
  // webhook-* headers; fall back to svix-* when webhook-id is absent (Hookdeck reads either)
  const p = h['webhook-id'] ? 'webhook' : 'svix';
  new Webhook(secret).verify(rawBody, {
    'webhook-id': h[`${p}-id`],
    'webhook-timestamp': h[`${p}-timestamp`],
    'webhook-signature': h[`${p}-signature`],
  }, { jsonParse: false }); // throws on a bad signature or stale timestamp -> respond 400
}

```

`BASIQ_WEBHOOK_SECRET` holds the full `whsec_...` value. Read the body raw (`express.raw({ type: '*/*' })`) and parse it only after the check.

> **Retries and replays.** The library always enforces a fixed **5-minute** timestamp window. Hookdeck retries and replays resend the *original* `webhook-timestamp`, so the library rejects a late retry after 5 minutes. If deliveries reach you through Hookdeck, or you replay old events, use the manual verifier in [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/basiq-webhooks/references/verification.md), where the window can be dropped or widened.

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

## Event Envelope

```json
{
  "eventTypeId": "user.created",
  "eventId": "f2f567b6bbb213c40a2875105327bb37b44a38ab8861b64074e2978b98d1f7c7",
  "links": {
    "event": "https://au-api.basiq.io/events/f2f567b6bbb2...",
    "eventEntity": "https://au-api.basiq.io/users/88a85f64-5717-4562-b3fc-2c963f66af88"
  }
}

```

The event type is the top-level `eventTypeId`. No resource data is inlined: call `links.event` or `links.eventEntity` (with your Basiq API token) to get it. Dedupe on the `webhook-id` header.

## Common Event Types

Basiq documents these 15 event types. The list may not be exhaustive: `GET /events/types` returns the current set.

| Group | Events |
| --- | --- |
| User | `user.created`, `user.updated`, `user.deleted` |
| Connection | `connection.created`, `connection.activated`, `connection.invalidated`, `connection.deleted` |
| Consent | `consent.created`, `consent.updated`, `consent.revoked`, `consent.expired`, `consent.reminder`, `consent.warning` |
| Data | `account.updated`, `transactions.updated` |

> **Details:** [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/basiq-webhooks/references/overview.md) and [Basiq's webhook docs](https://api.basiq.io/docs/webhooks).

## Delivery Notes

* Basiq documents no retry schedule, response timeout or ordering guarantee. Acknowledge with a `2xx` quickly, queue the work, and make handlers idempotent.
* A new subscription can take up to **15 minutes** to start delivering.
* Sent messages are listable with `GET /notifications/webhooks/{webhookId}/messages`.
* Deliveries come from `13.238.192.210`, `3.24.252.173` and `3.104.17.39`.

## Environment Variables

```bash
BASIQ_WEBHOOK_SECRET=whsec_xxxxx   # per-endpoint signing secret; handlers fail closed (500) without it

```

The `POST /notifications/webhooks` response includes the endpoint's `secret`. See [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/basiq-webhooks/references/setup.md).

## Local Development

```bash
# Express / Next.js (use 8000 for FastAPI). No install or account required.
npx hookdeck-cli listen 3000 basiq --path /webhooks/basiq

```

Create a subscription pointing at the printed URL, then send a test message with `POST /notifications/messages/test`.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/basiq-webhooks/references/overview.md) - Event catalog, thin payload, delivery notes
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/basiq-webhooks/references/setup.md) - Creating subscriptions with the API, secret, test messages
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/basiq-webhooks/references/verification.md) - Library and manual verification, header fallback, debugging