# Security

> How to keep your FX-Port integration secure: key handling, booking authorization and general API security hygiene.

Source: https://fx-port.com/docs/security

> [!WARNING]
> An FX-Port API key with **Read+Write** permission can create bookings and trigger real financial
> debits from your agency balance. Treat it with the same care as a payment credential.


## Always keep keys server-side

**Never expose your FX-Port API key in:**
- Browser JavaScript (React client components, Vue, Angular, plain JS)
- Mobile apps (React Native, Expo, Flutter, native iOS/Android)
- Public repositories, commit history, or CI/CD environment variables visible in build logs
- Client-side `.env` files shipped with a compiled app

Your app's **backend** (Node.js, Python, Go, etc.) should be the only code that ever reads and uses
the FX-Port API key. If your front-end needs to trigger a booking, it should call **your own
authenticated backend**, which then calls FX-Port on the user's behalf.

~~~
Browser/App  →  Your backend  →  FX-Port API
                    ↑
           API key lives here only
~~~

## Minimal-permission keys

Use a **Read-only** key for integrations that only display search results, balances, or status.
Reserve the **Read+Write** key for the backend service that actually creates bookings. See
[Authentication](https://fx-port.com/docs/authentication.md) for what each permission level allows.

## Key rotation

Rotate keys immediately if you suspect a leak. The dashboard lets you invalidate and replace a
key without downtime. Rotate on a routine schedule too (e.g. every 90 days) rather than only after
an incident — routine rotation limits how long a silently-leaked key stays useful to an attacker.

## Enforce booking ownership in your own backend


> [!WARNING]
> FX-Port scopes every response to the agency that owns the API key — one agency's key can never
> return another agency's bookings, balances, or account data. But that only protects the FX-Port
> API boundary. **If your own application lets multiple end-clients share one FX-Port key**
> (a common pattern), you are responsible for authorization **inside your own backend** so one
> client cannot view, modify, or cancel another client's booking.


A booking reference, order ID, ICCID, or PNR is **not a secret** — it can be guessed, enumerated,
logged, or leaked through a shared link. Never treat "the caller supplied a valid booking ID" as
proof that the caller is entitled to see it. Concretely:

- Store which of **your own** end-users/clients created or is entitled to each booking (a
  `client_id` / `user_id` column alongside the FX-Port `booking_id` you save).
- On every read or mutation your backend exposes (get booking, get ticket, cancel, pay, request a
  refund, view usage, etc.), check that the authenticated caller in **your** system owns that
  booking **before** forwarding the request to FX-Port — do not rely on the booking ID alone as an
  access-control token.
- Apply the same rule to balances, transaction/financial history, and account data: scope every
  query by the authenticated caller's own identity, never by an ID taken directly from the request
  path or query string.
- If you use a database with row-level security (Supabase, Postgres RLS, etc.), enforce this at the
  database layer as well as in application code — defense in depth. A backend bug that forgets an
  `owner_id` filter should not be enough, on its own, to expose another client's data.
- Test this explicitly: as client A, try to fetch/cancel a booking that belongs to client B by ID.
  It should fail with `403`/`404`, not succeed. Treat this as a required test case for every
  booking, balance, and account endpoint you expose — for flights, hotels, eSIMs, and any other
  product — not an edge case to defer.

This is Broken Object Level Authorization (OWASP API Security Top 10, API1:2023) — the single most
common vulnerability class in booking/travel APIs. It is entirely preventable with a per-request
ownership check and is far cheaper to build in from day one than to retrofit after a data leak.

## Validate and sanitize callback payloads

FX-Port does not send webhooks to API customers: poll a booking for its status. But if your application
receives callbacks from anyone (a payment provider, your own front-end, a partner), treat every
incoming payload as untrusted input, not as fact:

- Verify a signature/secret on every callback before acting on it, rather than trusting the sender
  because the request "looks right".
- Re-fetch authoritative state from the FX-Port API before making a financial or booking decision
  based on a callback payload, instead of trusting fields inside the callback body directly.
- Make callback handlers idempotent — a retried or duplicated delivery should not double-book,
  double-refund, or double-charge.

## Links to tickets, vouchers and documents are not private

The links the API returns for files (`bucket_link` and `voucher_url` for tickets and vouchers, `download_url` for uploaded
ACD documents) are plain HTTPS links that open **without a key**. Anyone who has the link can read the file, and a ticket,
a voucher or a passport scan holds personal data.

- Treat each link like a secret: do not put it in a public page, an e-mail to a third party, a URL that gets logged or an
  analytics event.
- Serve files to your own users through your authenticated backend (fetch the link server-side and stream it, after your
  ownership check) instead of handing the link to a browser.
- Do not keep the links for longer than you need them: fetch the booking again when you need a fresh one.

## Rate limiting and abuse protection

FX-Port limits each key per minute (see [Rate limits](https://fx-port.com/docs/rate-limits.md)) and refuses malformed requests before they reach a
supplier. Search and pricing endpoints are inexpensive to call but not free to abuse:

- Apply your own rate limiting in front of any public-facing feature that triggers an FX-Port call
  on user input (a search box, a price checker), so a single abusive user or bot can't exhaust your
  key's rate limit or run up unnecessary supplier calls.
- Cache read-only, non-personalized responses (e.g. [suppliers](https://fx-port.com/docs/suppliers.md) or [airports](https://fx-port.com/docs/flights/airports.md))
  client-side or at your edge where appropriate, instead of re-querying on every page load.

## Logging and data retention

- Never log full API keys, even in debug/error logs — log a short prefix (`fxp_live_ab12…`) if you
  need to identify which key made a call.
- Log the `request_id` of every FX-Port call (the `X-Request-Id` header, also in every body) next to your own
  ids: it is what support needs to find a request.
- Avoid logging full traveler PII (passport numbers, full card/payment details) in plaintext
  application logs. If you must retain it for support, encrypt it at rest and restrict access.
- Apply a retention policy to logs and cached search/pricing responses containing personal data,
  rather than keeping it indefinitely by default.

## Broader security hygiene


> [!NOTE]
> These rules apply to every sensitive credential in your application stack, not just FX-Port keys.


- **Supabase / database API keys** — never expose the `service_role` key in a browser or mobile app.
  Only the `anon` key can be used client-side, and even that should be behind Row-Level Security policies.
- **Third-party vendor keys** (Stripe, Twilio, etc.) — always consumed server-side. A leaked Stripe
  secret key or Twilio auth token can cause direct financial loss.
- **WordPress and open-source CMS plugins** — themes and plugins are a frequent attack vector.
  Credentials stored in a WordPress database are regularly exfiltrated through plugin vulnerabilities.
  If you run a CMS alongside your booking integration, keep credentials in separate, isolated
  infrastructure and audit installed plugins regularly.
- **Dependency CVEs** — keep your runtime and framework versions current. Subscribe to security
  advisories for your stack ([nodejs.org/en/security](https://nodejs.org/en/security),
  [github.com/advisories](https://github.com/advisories)) and apply patches promptly.
- **Least privilege everywhere** — the same "minimal-permission key" principle above applies to
  cloud IAM roles, CI/CD service accounts, and internal admin tooling. A credential should only be
  able to do what its specific job requires.

## Questions?

Found a vulnerability or want a security review of your integration? Email
[dev@fx-port.com](mailto:dev@fx-port.com).
