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

# Security

> How BlooBank secures the API — signed requests, environment isolation, and your security obligations.

BlooBank does not use static API keys. Every request to the API is **cryptographically signed** with an ECDSA `secp256k1` private key held by you. Static credentials, once leaked, give an attacker unlimited time; a signature, once leaked, is worthless — it is bound to one request, one moment, one body.

## What protects your account

<CardGroup cols={2}>
  <Card title="Asymmetric cryptography" icon="shield-check">
    BlooBank verifies signatures with your **public** key. Your **private** key never leaves your environment. There is no shared secret that both sides hold.
  </Card>

  <Card title="Replay protection" icon="clock-rotate-left">
    Every request carries a unique `X-Access-Request-Id` deduplicated for 1 hour. Captured signatures cannot be replayed.
  </Card>

  <Card title="Clock-skew enforcement" icon="clock">
    Timestamps outside ±10 seconds are rejected. A captured request has a 10-second window — by then, the request id has expired.
  </Card>

  <Card title="TLS-only" icon="lock-keyhole">
    Every endpoint is HTTPS. TLS 1.2 minimum. Headers, bodies, and signatures all travel encrypted.
  </Card>
</CardGroup>

## How it works at a glance

```mermaid theme={"dark"}
sequenceDiagram
    autonumber
    participant I as Your service
    participant B as BlooBank

    I->>I: Generate ECDSA keypair locally
    I->>B: Send public key (onboarding, one-time)
    Note over I,B: For every request:
    I->>I: Build canonical string
    I->>I: Sign with private key
    I->>B: Send signed request (4 headers)
    B->>B: Verify signature with public key
    alt Valid
      B->>I: 2xx response
    else Invalid
      B->>I: 401 SIGNATURE_INVALID
    end
```

Even if an attacker intercepts a request mid-flight, they cannot mint a new signature without your private key. The key stays on your infrastructure; only signatures cross the wire.

## Your security obligations

You hold the private key. That carries responsibility.

### Never expose the private key

| Do                                                                                                              | Do not                                                   |
| --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| Store in a secret manager (AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault, Doppler). | Hardcode in source.                                      |
| Add `*.pem`, `.env`, `*.key` to `.gitignore` from day one.                                                      | Paste into chat or email.                                |
| Restrict file permissions (`chmod 600`).                                                                        | Store in client-side applications (browser, mobile app). |
| Use `direnv` / `.envrc` locally.                                                                                | Send to BlooBank — only the public key is needed.        |

### Isolate environments

Use **distinct keypairs for sandbox, staging, and production**. Never reuse a staging key in production. A breach in one environment must not cascade to others.

```text theme={"dark"}
sandbox     ─►  sandbox keypair  ─►  sandbox Access Key
staging     ─►  staging keypair  ─►  staging Access Key
production  ─►  production keypair  ─►  production Access Key
```

### Synchronize clocks

The ±10-second skew tolerance is **tighter than typical cloud SDKs** (which allow minutes). Every host that signs requests must run NTP:

| Platform        | Command                                                                                            |
| --------------- | -------------------------------------------------------------------------------------------------- |
| Linux (modern)  | `timedatectl set-ntp true`; verify `chronyc tracking`                                              |
| Linux (classic) | `systemctl enable --now chronyd`                                                                   |
| macOS           | NTP is enabled by default; verify `sudo sntp -sS time.apple.com`                                   |
| Kubernetes      | Use a CronJob or sidecar to sync, or rely on the node's NTP if your runtime has clock pass-through |
| AWS EC2         | Amazon Time Sync Service is enabled by default                                                     |

VMs that pause and resume can have their clocks jump. Verify clock-drift correction on resume.

### Rotate credentials periodically

Every 6–12 months, or immediately on suspicion of compromise:

<Steps>
  <Step title="Generate a new keypair on the same environment">
    See [Generate your keys](/get-started/authentication/generate-keys).
  </Step>

  <Step title="Register the new public key">
    Send to your BlooBank integration contact. You will receive a **new** Access Key.
  </Step>

  <Step title="Cut over traffic">
    Switch your application to the new credential. Verify all production paths.
  </Step>

  <Step title="Revoke the old credential">
    Request revocation from BlooBank account team. After this, the old key is permanently invalid.
  </Step>
</Steps>

There is no in-place rotation — every rotation produces a new Access Key. Intentional: revocation is unambiguous.

### Log diagnostics, not secrets

For every non-2xx response, log:

| Log                                                | Do not log                                                                         |
| -------------------------------------------------- | ---------------------------------------------------------------------------------- |
| HTTP method and URL (without secret query values). | Credentials of any kind.                                                           |
| `error.status` and `error.code`.                   | The private key.                                                                   |
| Every `details[].reason`.                          | The Base64 signature alongside the canonical request (lets an observer correlate). |
| `metadata.decisionId` if present.                  | Full request bodies containing user PII.                                           |
| `metadata.id` from `ERROR_RECORDED` if present.    | Headers (`X-Access-Signature` value).                                              |
| `X-Access-Request-Id` (your correlation handle).   |                                                                                    |
| Approximate UTC time.                              |                                                                                    |

### Verify webhooks before acting

Webhook deliveries are server-pushed and must be verified before processing — see [Verifying signatures](/get-started/webhooks/signature-verification). An unverified webhook is an attack surface.

## Environments

Access credentials and signing keys are tied to the environment in which they were issued. Before going live in production:

1. Validate all integration flows in sandbox/staging.
2. Verify authentication, request signing, response handling, and error handling are correct.
3. Confirm webhook signature verification works end-to-end.
4. Run load tests at expected peak traffic.
5. Verify clock sync on all hosts that will sign requests.

## When something goes wrong

| Symptom                                  | First action                                                                                                  |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `SIGNATURE_INVALID` on every request     | Walk the [11-step troubleshooting checklist](/get-started/authentication/troubleshooting).                    |
| `TIMESTAMP_SKEW_EXCEEDED` intermittently | Check NTP on every signing host; check VM resume behavior.                                                    |
| Suspected key compromise                 | **Rotate immediately.** Generate a new keypair, request a new Access Key, then request revocation of the old. |
| Unexplained `RBAC_DENY`                  | Capture `metadata.decisionId`; contact your account team with the id.                                         |
| `INTERNAL` errors                        | Capture `metadata.id` from `ERROR_RECORDED`; contact support with the id.                                     |

## Next

<CardGroup cols={3}>
  <Card title="Authentication" icon="shield-check" href="/get-started/authentication/overview">
    The Access Protocol in detail.
  </Card>

  <Card title="Webhook signature verification" icon="signature" href="/get-started/webhooks/signature-verification">
    How to verify server-pushed deliveries.
  </Card>

  <Card title="Errors" icon="bug" href="/get-started/errors/overview">
    What to do when something fails.
  </Card>
</CardGroup>
