<!-- ZenPays documentation · https://docs.zenpayz.com/docs/rest-api/authentication -->

# REST API Authentication

All REST API requests must include authentication headers. ZenPays uses **API Key + HMAC Signature** authentication for secure request verification.

## Required Headers

| Header | Required | Description |
|--------|----------|-------------|
| `Authorization` | Yes | `Bearer {api_key}` - Your API key |
| `X-Signature` | Yes | HMAC-SHA256 signature of the request |
| `X-Timestamp` | Yes | ISO 8601 timestamp (UTC) |
| `X-Secret-Salt` | Yes | Your secret salt for HMAC signature validation |
| `Content-Type` | Yes* | `application/json` for POST/PUT/PATCH requests |
| `X-Merchant-ID` | No | Optional merchant ID for multi-merchant setups |

## API Keys

ZenPays uses prefixed API keys to identify the environment:

| Prefix | Environment | Usage |
|--------|-------------|-------|
| `zp_live_` | Production | Real payments |
| `zp_test_` | Sandbox | Testing and development |

### Getting Your API Key

1. Log in to your [ZenPays Dashboard](https://dashboard.zenpays.com)
2. Navigate to **Settings** → **API Keys**
3. Copy your API key and Secret Salt

:::warning
Keep your API keys and Secret Salt secure. Never expose them in client-side code or commit them to version control.
:::

## Signature Generation

Every request must include an HMAC-SHA256 signature to verify its integrity.

### Signature Formula

```
signature = HMAC-SHA256(timestamp + body, secret_salt)
```

Where:
- `timestamp` - The value of the `X-Timestamp` header
- `body` - JSON stringified request body (empty string for GET requests)
- `secret_salt` - Your secret salt from the dashboard

### Implementation Examples

<Tabs groupId="language">
  <TabItem value="javascript" label="JavaScript" default>

```javascript
const crypto = require('crypto');

function generateSignature(timestamp, body, secretSalt) {
  const data = timestamp + JSON.stringify(body);
  return crypto
    .createHmac('sha256', secretSalt)
    .update(data)
    .digest('hex');
}

// Example usage
const timestamp = new Date().toISOString();
const body = { amount: 1000, currency: 'USD' };
const signature = generateSignature(
  timestamp,
  body,
  process.env.ZENPAYS_SECRET_SALT
);

console.log('Signature:', signature);
// Output: a1b2c3d4e5f6...
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import hmac
import hashlib
import json
import os
from datetime import datetime

def generate_signature(timestamp: str, body: dict, secret_salt: str) -> str:
    data = timestamp + json.dumps(body, separators=(",", ":"))
    return hmac.new(
        secret_salt.encode(),
        data.encode(),
        hashlib.sha256
    ).hexdigest()

# Example usage
timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z")
body = {"amount": 1000, "currency": "USD"}
signature = generate_signature(
    timestamp,
    body,
    os.environ["ZENPAYS_SECRET_SALT"]
)

print(f"Signature: {signature}")
# Output: a1b2c3d4e5f6...
```

  </TabItem>
  <TabItem value="go" label="Go">

```go
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "time"
)

func generateSignature(timestamp string, body interface{}, secretSalt string) string {
    bodyBytes, _ := json.Marshal(body)
    data := timestamp + string(bodyBytes)

    h := hmac.New(sha256.New, []byte(secretSalt))
    h.Write([]byte(data))
    return hex.EncodeToString(h.Sum(nil))
}

// Example usage
func main() {
    timestamp := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
    body := map[string]interface{}{
        "amount":   1000,
        "currency": "USD",
    }
    signature := generateSignature(timestamp, body, "your_secret_salt")
    // Use signature in X-Signature header
}
```

  </TabItem>
  <TabItem value="php" label="PHP">

```php
<?php

function generateSignature(string $timestamp, array $body, string $secretSalt): string {
    $data = $timestamp . json_encode($body, JSON_UNESCAPED_SLASHES);
    return hash_hmac('sha256', $data, $secretSalt);
}

// Example usage
$timestamp = gmdate('Y-m-d\TH:i:s.000\Z');
$body = ['amount' => 1000, 'currency' => 'USD'];
$signature = generateSignature(
    $timestamp,
    $body,
    getenv('ZENPAYS_SECRET_SALT')
);

echo "Signature: $signature\n";
```

  </TabItem>
</Tabs>

## Complete Request Example

<Tabs groupId="language">
  <TabItem value="curl" label="cURL" default>

```bash
#!/bin/bash

API_KEY="zp_test_xxxxx"
SECRET_SALT="your_secret_salt"
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")

BODY='{"amount":1000,"currency":"USD","description":"Order #123"}'

# Generate signature (requires openssl)
SIGNATURE=$(echo -n "${TIMESTAMP}${BODY}" | openssl dgst -sha256 -hmac "$SECRET_SALT" | cut -d' ' -f2)

curl -X POST https://api.zenpayz.com/api/v1/payment-intents \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: $SIGNATURE" \
  -H "X-Secret-Salt: $SECRET_SALT" \
  -H "Content-Type: application/json" \
  -d "$BODY"
```

  </TabItem>
  <TabItem value="javascript" label="JavaScript">

```javascript
const crypto = require('crypto');

async function createPaymentIntent() {
  const apiKey = process.env.ZENPAYS_API_KEY;
  const secretSalt = process.env.ZENPAYS_SECRET_SALT;
  const timestamp = new Date().toISOString();

  const body = {
    amount: 1000,
    currency: 'USD',
    description: 'Order #123',
  };

  // Generate signature
  const data = timestamp + JSON.stringify(body);
  const signature = crypto
    .createHmac('sha256', secretSalt)
    .update(data)
    .digest('hex');

  const response = await fetch('https://api.zenpayz.com/api/v1/payment-intents', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'X-Timestamp': timestamp,
      'X-Signature': signature,
      'X-Secret-Salt': secretSalt,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });

  return response.json();
}
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
import hmac
import hashlib
import json
import os
from datetime import datetime
import requests

def create_payment_intent():
    api_key = os.environ["ZENPAYS_API_KEY"]
    secret_salt = os.environ["ZENPAYS_SECRET_SALT"]
    timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z")

    body = {
        "amount": 1000,
        "currency": "USD",
        "description": "Order #123",
    }

    # Generate signature
    data = timestamp + json.dumps(body, separators=(",", ":"))
    signature = hmac.new(
        secret_salt.encode(),
        data.encode(),
        hashlib.sha256
    ).hexdigest()

    response = requests.post(
        "https://api.zenpayz.com/api/v1/payment-intents",
        headers={
            "Authorization": f"Bearer {api_key}",
            "X-Timestamp": timestamp,
            "X-Signature": signature,
            "X-Secret-Salt": secret_salt,
            "Content-Type": "application/json",
        },
        json=body,
    )

    return response.json()
```

  </TabItem>
</Tabs>

## Timestamp Validation

The `X-Timestamp` header must be within **±5 minutes** of the server time. Requests with timestamps outside this window are rejected to prevent replay attacks.

```json
{
  "success": false,
  "error": {
    "code": "TIMESTAMP_EXPIRED",
    "message": "Request timestamp is outside the allowed window"
  }
}
```

:::tip
Always use UTC timestamps and ensure your server clock is synchronized with NTP.
:::

## GET Request Authentication

For GET requests without a body, use an empty object `{}` when calculating the signature:

<Tabs groupId="language">
  <TabItem value="javascript" label="JavaScript" default>

```javascript
// For GET requests
const timestamp = new Date().toISOString();
const data = timestamp + '{}';
const signature = crypto
  .createHmac('sha256', secretSalt)
  .update(data)
  .digest('hex');

const response = await fetch('https://api.zenpayz.com/api/v1/payment-intents/pi_xxx', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'X-Timestamp': timestamp,
    'X-Signature': signature,
    'X-Secret-Salt': secretSalt,
  },
});
```

  </TabItem>
  <TabItem value="python" label="Python">

```python
# For GET requests
timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z")
data = timestamp + "{}"
signature = hmac.new(
    secret_salt.encode(),
    data.encode(),
    hashlib.sha256
).hexdigest()

response = requests.get(
    "https://api.zenpayz.com/api/v1/payment-intents/pi_xxx",
    headers={
        "Authorization": f"Bearer {api_key}",
        "X-Timestamp": timestamp,
        "X-Signature": signature,
        "X-Secret-Salt": secret_salt,
    },
)
```

  </TabItem>
</Tabs>

## IP Whitelisting

For enhanced security, you can restrict API access to specific IP addresses:

1. Go to **Dashboard** → **Settings** → **API Security**
2. Add your server IP addresses to the whitelist
3. Enable IP restriction for your API keys

Requests from non-whitelisted IPs will receive:

```json
{
  "success": false,
  "error": {
    "code": "IP_NOT_WHITELISTED",
    "message": "Request from unauthorized IP address"
  }
}
```

## Authentication Errors

| Error Code | HTTP Status | Description |
|------------|-------------|-------------|
| `UNAUTHORIZED` | 401 | Missing or invalid API key |
| `INVALID_SIGNATURE` | 401 | Signature verification failed |
| `TIMESTAMP_EXPIRED` | 401 | Timestamp outside allowed window |
| `API_KEY_REVOKED` | 401 | API key has been revoked |
| `IP_NOT_WHITELISTED` | 403 | Request from unauthorized IP |
| `INSUFFICIENT_PERMISSIONS` | 403 | API key lacks required scope |

## Best Practices

1. **Use environment variables** - Never hardcode API keys or secrets
2. **Rotate keys regularly** - Create new keys and revoke old ones periodically
3. **Use minimal scopes** - Only grant necessary permissions to each key
4. **Enable IP whitelisting** - Restrict access to known server IPs
5. **Monitor API usage** - Check the dashboard for unusual activity
6. **Use HTTPS only** - Never send credentials over unencrypted connections

## Next Steps

- [Error Handling](/docs/rest-api/errors) - Learn about error responses
- [SDK Authentication](/docs/getting-started/authentication) - Simpler auth with the SDK
- [Webhooks](/docs/guides/webhooks) - Verify webhook signatures
