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

# Authentication

> Secure your API requests with API Key authentication

## Authentication Overview

Waffo Pancake supports two authentication methods for API access:

| Method         | Use Case               | Description                                                    |
| -------------- | ---------------------- | -------------------------------------------------------------- |
| **API Key**    | Server-to-server calls | Permanent authentication using RSA-SHA256 signatures           |
| **Store Slug** | Public checkout flows  | Public access using `X-Store-Slug` and `X-Environment` headers |

***

## API Key Authentication

API Keys provide permanent server-to-server authentication using RSA-SHA256 signatures. The private key never leaves your server.

### Request Headers

```bash theme={"system"}
X-Merchant-Id: MER_2aUyqjCzEIiEcYMKj7TZtw
X-Timestamp: 1705312200
X-Signature: BASE64_ENCODED_SIGNATURE
Content-Type: application/json
```

<Note>
  **API Key authentication does not require the `X-Environment` header.** Each API Key is bound to either test or prod at creation time. The environment is determined by which key successfully verifies the signature.
</Note>

### Using the SDK (Recommended)

```typescript theme={"system"}
import { WaffoPancake } from "@waffo/pancake-ts";

const client = new WaffoPancake({
  merchantId: process.env.WAFFO_MERCHANT_ID!,
  privateKey: process.env.WAFFO_PRIVATE_KEY!,
});

// All requests are automatically signed
const { store } = await client.stores.create({ name: "My Store" });
```

### Signing Algorithm (Manual Integration)

If you're not using the SDK, you need to implement RSA-SHA256 request signing:

```
1. Build the canonical request:
   canonicalRequest = METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + SHA256_BASE64(BODY)

2. Sign with RSA-SHA256:
   signature = RSA-SHA256(canonicalRequest, privateKey)

3. Base64 encode:
   X-Signature = Base64(signature)
```

### Manual Signing Examples

<CodeGroup>
  ```javascript Node.js theme={"system"}
  const crypto = require('crypto');

  const MERCHANT_ID = 'MER_2aUyqjCzEIiEcYMKj7TZtw';
  const PRIVATE_KEY = `-----BEGIN RSA PRIVATE KEY-----
  ...your private key...
  -----END RSA PRIVATE KEY-----`;

  async function callApiWithSignature(method, path, body) {
    const timestamp = Math.floor(Date.now() / 1000).toString();
    const bodyStr = JSON.stringify(body);
    const bodyHash = crypto.createHash('sha256').update(bodyStr).digest('base64');

    // Build canonical request
    const canonicalRequest = `${method}\n${path}\n${timestamp}\n${bodyHash}`;

    // RSA-SHA256 sign
    const signature = crypto.sign('sha256', Buffer.from(canonicalRequest), PRIVATE_KEY).toString('base64');

    const response = await fetch(`https://api.waffo.ai${path}`, {
      method,
      headers: {
        'Content-Type': 'application/json',
        'X-Merchant-Id': MERCHANT_ID,
        'X-Timestamp': timestamp,
        'X-Signature': signature
      },
      body: bodyStr
    });

    return response.json();
  }

  // Usage
  const result = await callApiWithSignature('POST', '/v1/actions/store/create-store', {
    name: 'My Store'
  });
  ```

  ```bash cURL theme={"system"}
  MERCHANT_ID="MER_2aUyqjCzEIiEcYMKj7TZtw"
  TIMESTAMP=$(date +%s)
  BODY='{"name":"My Store"}'
  BODY_HASH=$(echo -n "$BODY" | openssl dgst -sha256 -binary | base64 -w 0)
  CANONICAL_REQUEST="POST
  /v1/actions/store/create-store
  $TIMESTAMP
  $BODY_HASH"

  # Sign with openssl (requires private_key.pem file)
  SIGNATURE=$(echo -n "$CANONICAL_REQUEST" | openssl dgst -sha256 -sign private_key.pem | base64 -w 0)

  curl -X POST "https://api.waffo.ai/v1/actions/store/create-store" \
    -H "Content-Type: application/json" \
    -H "X-Merchant-Id: $MERCHANT_ID" \
    -H "X-Timestamp: $TIMESTAMP" \
    -H "X-Signature: $SIGNATURE" \
    -d "$BODY"
  ```

  ```python Python theme={"system"}
  import hashlib
  import time
  import base64
  import json
  import requests
  from cryptography.hazmat.primitives import hashes, serialization
  from cryptography.hazmat.primitives.asymmetric import padding

  MERCHANT_ID = 'MER_2aUyqjCzEIiEcYMKj7TZtw'
  PRIVATE_KEY = '''-----BEGIN RSA PRIVATE KEY-----
  ...your private key...
  -----END RSA PRIVATE KEY-----'''

  def call_api_with_signature(method, path, body):
      timestamp = str(int(time.time()))
      body_str = json.dumps(body, separators=(',', ':'))
      body_hash = base64.b64encode(hashlib.sha256(body_str.encode()).digest()).decode()

      # Build canonical request
      canonical_request = f"{method}\n{path}\n{timestamp}\n{body_hash}"

      # RSA-SHA256 sign
      private_key = serialization.load_pem_private_key(PRIVATE_KEY.encode(), password=None)
      signature = private_key.sign(canonical_request.encode(), padding.PKCS1v15(), hashes.SHA256())
      signature_b64 = base64.b64encode(signature).decode()

      response = requests.post(
          f'https://api.waffo.ai{path}',
          headers={
              'Content-Type': 'application/json',
              'X-Merchant-Id': MERCHANT_ID,
              'X-Timestamp': timestamp,
              'X-Signature': signature_b64
          },
          data=body_str
      )

      return response.json()

  # Usage
  result = call_api_with_signature('POST', '/v1/actions/store/create-store', {
      'name': 'My Store'
  })
  ```
</CodeGroup>

<Warning>
  **Private Key Security**

  * Never expose the private key in client-side code
  * Do not commit private keys to version control
  * Store private keys in environment variables
  * Rotate keys regularly, especially after team changes
  * Timestamp must be within **5 minutes** of server time
</Warning>

***

## Store Slug Authentication

For public-facing checkout flows, use Store Slug authentication. This allows visitors to create checkout sessions and query public store data without API Key credentials.

### Request Headers

```bash theme={"system"}
X-Store-Slug: my-awesome-store-k8x2m9ab
X-Environment: test | prod
Content-Type: application/json
```

### Example

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -X POST https://api.waffo.ai/v1/actions/checkout/create-session \
    -H "X-Store-Slug: my-awesome-store-k8x2m9ab" \
    -H "X-Environment: test" \
    -H "Content-Type: application/json" \
    -d '{"productId": "PROD_4cWAslE1GKkGeaOMl9Vbmy", "productType": "onetime", "currency": "USD"}'
  ```

  ```javascript JavaScript theme={"system"}
  const response = await fetch('https://api.waffo.ai/v1/graphql', {
    method: 'POST',
    headers: {
      'X-Store-Slug': 'my-awesome-store-k8x2m9ab',
      'Content-Type': 'application/json',
      'X-Environment': 'prod'
    },
    body: JSON.stringify({
      query: `query { store { id name onetimeProducts { id name prices } } }`
    })
  });
  ```
</CodeGroup>

***

## Creating API Keys

<Steps>
  <Step title="Go to API & Development Page">
    Navigate to Dashboard → API & Development → API Keys
  </Step>

  <Step title="Create API Key">
    Click "Create API Key" to generate a new RSA key pair. The public key is sent to the server automatically.
  </Step>

  <Step title="Name and Configure">
    Give it a descriptive name (e.g., "Production Server") and select the target environment (Test or Production).
  </Step>

  <Step title="Download Private Key">
    **Download your private key immediately.** It will not be shown again.
  </Step>
</Steps>

<Warning>
  Deleting an API key is immediate and irreversible. Any requests signed with the deleted key will fail with `401 Unauthorized`.
</Warning>

***

## Authentication Method Comparison

| Feature                 |           API Key          |       Store Slug       |
| ----------------------- | :------------------------: | :--------------------: |
| Server-side use         |             Yes            |           No           |
| Client-side use         |             No             |           Yes          |
| Requires X-Environment  |             No             |           Yes          |
| Issue Session Token     |             Yes            |           No           |
| Create Checkout Session |             Yes            |           Yes          |
| Product Management      |             Yes            |           No           |
| GraphQL Queries         |             Yes            | Yes (public data only) |
| Validity                | Permanent (key-controlled) |            -           |

***

## Authentication Errors

| Status | Error                    | Solution                                                                     |
| ------ | ------------------------ | ---------------------------------------------------------------------------- |
| 401    | Invalid signature        | Check signing algorithm, private key, and timestamp freshness (5 min window) |
| 401    | Invalid or expired token | Re-authenticate or use a valid API Key                                       |
| 403    | Insufficient permissions | Check if the role has access to the endpoint                                 |
| 400    | Missing authentication   | Ensure required headers are present                                          |

```json theme={"system"}
{
  "data": null,
  "errors": [
    {
      "message": "Invalid signature",
      "layer": "gateway"
    }
  ]
}
```

***

## Security Best Practices

1. **Never expose private keys** in client-side code, version control, or public repositories
2. **Use HTTPS** for all API requests
3. **Verify Webhook signatures** to prevent forged requests
4. **Separate test and production keys** -- create distinct keys for each environment
5. **Rotate keys regularly**, especially after team member changes
6. **Monitor API usage** in the Dashboard for unusual activity
