> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rails.wayex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Developers

> Manage API keys, your developer fee, and webhook subscriptions for your programmatic integration.

The console is a UI over the same API you can call directly. The **Developers** area is where you manage
what powers your integration: your **API keys**, your **developer fee**, and your **webhook endpoints**.

<Note>
  This page covers the developer surface at a product level. For request and response shapes and the
  full endpoint list, see the [API reference](/api-reference/overview).
</Note>

## API keys

Every API request authenticates with an **API key**. There are two kinds:

| Key                 | Prefix | Use it for                                                                                                              |
| ------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------- |
| **Publishable key** | `pk_…` | Documented non-sensitive read-only calls. Do not use it to expose Treasury or beneficiary data in an untrusted browser. |
| **Secret key**      | `sk_…` | Server-side only. Required for state-changing writes. Never expose it in a browser, mobile app, or public repository.   |

Send your key on every request as a bearer token:

```bash theme={null}
Authorization: Bearer YOUR_API_KEY
```

Or, equivalently, in the `X-Api-Key` header:

```bash theme={null}
X-Api-Key: YOUR_API_KEY
```

### Managing keys

From **Developers → API keys** you can:

* **Create** a key.
* **Rotate** a key — issue a replacement and retire the old one.
* **Revoke** a key — disable it immediately.

A key is bound to one tenant account. Reads and writes cannot select another tenant in a request.
Keep each tenant account's key in a separate secret.

A key's **scope** follows its configured access. Treasury reads require `treasury:read`; funding,
beneficiary, payout, quote, conversion, destination, and withdrawal writes require `treasury:write`.
A request made with an out-of-scope key is rejected.

<Warning>
  Treat your secret key like a password. The full secret value is shown **once**, at creation — copy
  it then and store it securely. If a key is ever exposed, rotate it immediately. Wayex will never
  ask you for your secret key.
</Warning>

## Developer fees

Your **developer fee** is your revenue: a markup you charge your end customers on each conversion, on
top of the rate. Configure it from **Developers → Fees** (or programmatically via `GET` / `PUT`
`/v1/developer-fees`) as a **flat AUD amount**, a **percentage in basis points**, or both, applied per
transfer.

The exchange rate you and your customers see is the **all-in rate Wayex quotes** — Wayex's margin is
inside that rate and is not your revenue. Your developer fee is the only fee surface you configure, and
it works like this:

* **Withheld from each conversion.** When a funded route converts, your fee is deducted from the
  conversion — the end customer receives the net **minus your fee**. The withheld amount is held owed
  to you.
* **Credited against your monthly invoice.** Each billing period, your accrued developer fees appear as
  a credit on your Wayex invoice, netted against the platform fees you owe — see
  [Billing and invoices](/console/activity).
* **Applies going forward.** A saved schedule applies to conversions struck **after** the change; it
  never reprices a conversion already in flight.
* **Capped.** A schedule above the per-transfer caps — **500 basis points** or **A\$100.00 flat** — is
  rejected outright (HTTP `422`), never silently reduced.
* **Never eats a transfer.** If a configured fee would consume an entire conversion, the fee for that
  transfer is reduced to zero and the conversion proceeds normally; the skip is recorded. A fee
  misconfiguration can never strand a customer's funds.

Setting an empty schedule means you charge no developer fee. Updating the schedule is value-affecting:
it requires a secret key (or a console role with write access), and every change is audited.

<Note>
  This developer fee applies to direct customer-route conversions. Treasury pricing and operation
  fees come from the effective settings for the authenticated tenant account.
</Note>

## Webhooks

Webhooks are how your systems learn about things that happen asynchronously — a customer's verification
outcome, a payment route being created, and every conversion a funded route spawns. Webhooks are the
signal that state changed: subscribe an endpoint, verify the event, then fetch the current resource
as the authoritative state.

### Subscribe your endpoint

Register an HTTPS URL on your server to receive events. Wayex sends each event as an HTTP `POST` with a
JSON body.

### Event shape

Every event carries a stable **`eventId`**, a **`type`**, a **`createdAt`** timestamp, and a typed
**`data`** payload. The event types you can receive are:

| Event                            | Fires when                                                                      |
| -------------------------------- | ------------------------------------------------------------------------------- |
| `counterparty.created`           | A counterparty is created.                                                      |
| `counterparty.invited`           | A counterparty is invited to the console.                                       |
| `counterparty.activated`         | A counterparty completes activation.                                            |
| `customer.created`               | A customer is created.                                                          |
| `kyc.updated`                    | A customer's verification status changes (and entitlements update on approval). |
| `payment_route.created`          | A payment route is created.                                                     |
| `payment_route.activity.created` | A deposit, payout, or other event occurs on a route.                            |
| `rate.updated`                   | An indicative rate changes.                                                     |
| `transfer.created`               | A funded route spawns a conversion.                                             |
| `transfer.updated`               | A transfer changes state.                                                       |
| `transfer.completed`             | A transfer completes.                                                           |
| `treasury.wallet.updated`        | A Treasury wallet balance or reservation changes.                               |
| `treasury.payin.updated`         | An incoming AUD pay-in changes state.                                           |
| `treasury.deposit.updated`       | An incoming stablecoin deposit changes state.                                   |
| `treasury.payout.updated`        | An AUD payout changes state.                                                    |
| `treasury.withdrawal.updated`    | A stablecoin withdrawal changes state.                                          |
| `treasury.conversion.updated`    | A Treasury conversion or physical settlement leg changes state.                 |

<Note>
  Transfers are created automatically when a route is funded — Wayex converts at the live rate the
  instant funds land. You never create a transfer yourself, so there is no quote to accept and no
  price to lock; the binding price is struck fresh at funding. The Rates API and the console Rates
  screen are live indicative only and never bind.
</Note>

### Verify the signature

Each delivery is **signed** so you can confirm it genuinely came from Wayex and was not tampered
with. Wayex sends the signature in the **`X-Wayex-Signature`** header, formatted as
`sha256=<hex>`, where `<hex>` is the **HMAC-SHA256** of the **raw request body** keyed by your
endpoint's signing secret (returned once when you create the endpoint).

<Steps>
  <Step title="Read the raw body">
    Capture the exact raw request bytes before any JSON parsing — the signature is computed over the
    raw body, so re-serializing it will change the result.
  </Step>

  <Step title="Compute your own HMAC">`HMAC-SHA256(rawBody, yourSigningSecret)`, hex-encoded.</Step>

  <Step title="Compare in constant time">
    Strip the `sha256=` prefix from the header and compare against your computed hex using a
    constant-time comparison. If they do not match, reject the request with a non-2xx.
  </Step>
</Steps>

```js theme={null}
import { createHmac, timingSafeEqual } from 'node:crypto';

function verifyWayexSignature(rawBody, header, signingSecret) {
  const expected = createHmac('sha256', signingSecret).update(rawBody).digest('hex');
  const received = (header ?? '').replace(/^sha256=/, '');
  const a = Buffer.from(expected);
  const b = Buffer.from(received);
  return a.length === b.length && timingSafeEqual(a, b);
}
```

### Handle deliveries safely

Delivery is **at-least-once** and **unordered**, so build your consumer to tolerate that:

* **Deduplicate** by `eventId` — you may receive the same event more than once.
* **Do not assume ordering** — events can arrive out of sequence. When you need the authoritative state of
  a resource, fetch it from the API rather than inferring it from event order.
* **Respond quickly** with a `2xx` to acknowledge receipt, then do heavier work asynchronously.

### Delivery logs

The Developers area shows recent **deliveries** for each endpoint — the event sent, the response your
endpoint returned, and whether it succeeded — so you can debug an integration and confirm events are being
received.

<Note>
  The sandbox is an integration environment and may use controlled live-provider rails for an
  explicitly enabled tenant. Confirm your environment and limits before funding; webhook handling
  should be treated as production code in every environment.
</Note>
