Authentication
Learn how to authenticate with the Starship Rewards API
Authentication Overview
The Starship Rewards API uses API Key authentication -- stateless, secure, and simple. Every request is authenticated with two headers. Optionally, you can add HMAC request signing for tamper-proof requests and verified webhook deliveries.
API Key Authentication
Send your API Key and Secret with every request:
X-API-Key: sk_live_your_api_key_here
X-API-Secret: your_api_secret_hereThat's it. No login step, no token management, no refresh logic.
Setup
- Log in to the Client Portal → Settings → API Keys
- Click Generate API Key & Secret on any credential
- Copy the API Secret immediately -- it is shown only once
- Store both values securely in your backend configuration (environment variables, secrets manager)
Example
# List products
curl -X GET {{host}}/api/v1/products \
-H "X-API-Key: sk_live_abc123def456" \
-H "X-API-Secret: your-secret-here" \
-H "Content-Type: application/json"
# Create an order
curl -X POST {{host}}/api/v1/orders \
-H "X-API-Key: sk_live_abc123def456" \
-H "X-API-Secret: your-secret-here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-order-id-123" \
-d '{
"product_id": 42,
"denomination": 100,
"quantity": 1,
"email": "recipient@example.com"
}'<?php
class StarshipAPI {
private string $apiKey;
private string $apiSecret;
private string $baseUrl;
public function __construct(string $apiKey, string $apiSecret, string $baseUrl = '{{host}}') {
$this->apiKey = $apiKey;
$this->apiSecret = $apiSecret;
$this->baseUrl = $baseUrl;
}
public function request(string $method, string $endpoint, ?array $data = null): array {
$headers = [
'X-API-Key: ' . $this->apiKey,
'X-API-Secret: ' . $this->apiSecret,
'Content-Type: application/json',
];
$context = stream_context_create([
'http' => [
'method' => $method,
'header' => implode("\r\n", $headers),
'content' => $data ? json_encode($data) : null,
]
]);
$response = file_get_contents($this->baseUrl . $endpoint, false, $context);
return json_decode($response, true);
}
public function getProducts(): array {
return $this->request('GET', '/api/v1/products');
}
public function createOrder(array $orderData, string $idempotencyKey): array {
return $this->request('POST', '/api/v1/orders', $orderData);
}
}
// Usage
$api = new StarshipAPI('sk_live_abc123def456', 'your-secret-here');
$products = $api->getProducts();import requests
class StarshipAPI:
def __init__(self, api_key: str, api_secret: str, base_url: str = "{{host}}"):
self.base_url = base_url
self.headers = {
"X-API-Key": api_key,
"X-API-Secret": api_secret,
"Content-Type": "application/json",
}
def get_products(self):
response = requests.get(f"{self.base_url}/api/v1/products", headers=self.headers)
response.raise_for_status()
return response.json()
def create_order(self, order_data: dict, idempotency_key: str):
headers = {**self.headers, "Idempotency-Key": idempotency_key}
response = requests.post(
f"{self.base_url}/api/v1/orders",
json=order_data,
headers=headers,
)
response.raise_for_status()
return response.json()
# Usage
api = StarshipAPI("sk_live_abc123def456", "your-secret-here")
products = api.get_products()const axios = require('axios');
class StarshipAPI {
constructor(apiKey, apiSecret, baseUrl = '{{host}}') {
this.client = axios.create({
baseURL: baseUrl,
headers: {
'X-API-Key': apiKey,
'X-API-Secret': apiSecret,
'Content-Type': 'application/json',
},
});
}
async getProducts() {
const { data } = await this.client.get('/api/v1/products');
return data;
}
async createOrder(orderData, idempotencyKey) {
const { data } = await this.client.post('/api/v1/orders', orderData, {
headers: { 'Idempotency-Key': idempotencyKey },
});
return data;
}
}
// Usage
const api = new StarshipAPI('sk_live_abc123def456', 'your-secret-here');
const products = await api.getProducts();Error Responses
| Status | Error | Meaning |
|---|---|---|
| 401 | X-API-Secret header required | API key sent without secret |
| 401 | Invalid API key | API key not found or inactive |
| 401 | Invalid API secret | Secret doesn't match |
| 403 | Forbidden | Valid auth but insufficient permissions |
The Signing Secret -- One Key, Two Purposes
When you generate an API credential, you receive three values:
| Value | Header | Purpose |
|---|---|---|
| API Key | X-API-Key | Identifies your credential |
| API Secret | X-API-Secret | Authenticates your requests |
| Signing Secret | Used in HMAC computation | Signs your API requests AND verifies webhook deliveries |
The signing secret (whsec_...) serves both purposes:
-
Inbound (your requests to us): When HMAC signing is enabled, you use the signing secret to compute an
X-Signatureheader on every API request. This proves the request hasn't been tampered with in transit. See HMAC Request Signing. -
Outbound (our webhooks to you): When you create a webhook linked to a credential, we use the same signing secret to sign webhook delivery payloads. You verify the signature to confirm the webhook came from Starship and hasn't been modified. See Webhooks.
Signing Secret (whsec_...)
|
+-------------+-------------+
| |
YOUR API REQUESTS WEBHOOK DELIVERIES
(you sign outbound) (we sign, you verify)
| |
X-Signature header X-Signature header
on requests to us on deliveries to youThe signing secret is separate from the API Secret. The API Secret authenticates WHO you are. The signing secret proves WHAT was sent hasn't been tampered with.
Migration Path -- Adopting HMAC Step by Step
HMAC request signing is optional today and controlled per credential by your admin. We recommend a gradual rollout:
Step 1: API Key Authentication (Start Here)
Get your integration working with X-API-Key + X-API-Secret. This is sufficient for most use cases and is already secure over HTTPS.
X-API-Key: sk_live_abc123def456
X-API-Secret: your-secret-hereStep 2: Enable Webhooks with Signature Verification
Register a webhook endpoint. The signing secret is returned at creation time. Verify inbound webhook signatures to confirm authenticity.
curl -X POST "{{host}}/api/v1/webhooks" \
-H "X-API-Key: sk_live_abc123def456" \
-H "X-API-Secret: your-secret-here" \
-H "Content-Type: application/json" \
-d '{
"name": "Order Updates",
"url": "https://your-endpoint.example.com/webhooks",
"events": ["order.delivered", "order.cancelled"],
"credential_id": 1
}'When credential_id is provided, the webhook uses that credential's signing secret for delivery signatures. This means you verify webhooks with the same key you'll later use for HMAC signing.
Step 3: Enable HMAC Request Signing
Ask your admin to enable HMAC on your credential (or it may be enabled by default for new credentials). From that point, every API request must include an X-Signature header.
X-API-Key: sk_live_abc123def456
X-API-Secret: your-secret-here
X-Signature: t=1740000000,v1=a1b2c3d4e5f6...See the full signing spec: HMAC Request Signing
Step 4: Rotate Signing Secrets
Periodically rotate your signing secret without downtime using the rotation endpoint:
curl -X POST "{{host}}/client/api/api-keys/1/rotate-signing-secret" \
-H "Cookie: session=your-portal-session"This generates a new signing secret. Update your integration, then the old secret stops working. Webhook deliveries automatically use the new secret.
API Endpoints Reference
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/api/v1/* | Any | X-API-Key + X-API-Secret | All API operations |
/api/v1/* | Any | + X-Signature (when HMAC enabled) | Tamper-proof requests |
Key Management (Client Portal)
These endpoints use cookie-based session auth from the Client Portal:
| Endpoint | Method | Description |
|---|---|---|
/client/api/api-keys | GET | List all API credentials |
/client/api/api-keys | POST | Create a new credential |
/client/api/api-keys/:id | GET | Get credential details |
/client/api/api-keys/:id | PUT | Update credential name |
/client/api/api-keys/:id | DELETE | Delete credential |
/client/api/api-keys/:id/generate-api-key | POST | Generate API key + secret + signing secret |
/client/api/api-keys/:id/revoke-api-key | POST | Revoke API key and secret |
/client/api/api-keys/:id/rotate-signing-secret | POST | Rotate signing secret only |
Legacy: JWT Token Authentication
JWT authentication is supported for backward compatibility only. All new integrations must use API Key authentication. JWT support may be deprecated in a future release.
JWT auth requires a login step to exchange credentials for short-lived tokens:
# Step 1: Login
POST {{host}}/auth/login
Content-Type: application/json
{"username": "your_username", "password": "your_password"}
# Returns: { "access_token": "...", "refresh_token": "..." }
# Step 2: Use access token
GET {{host}}/api/v1/products
Authorization: Bearer {access_token}
# Step 3: Refresh when expired
POST {{host}}/auth/refresh
Content-Type: application/json
{"refresh_token": "{refresh_token}"}
# Step 4: Logout
POST {{host}}/auth/logout
Authorization: Bearer {access_token}JWT tokens expire and require refresh logic. API Key auth avoids this complexity entirely. HMAC request signing is not available with JWT authentication.
Further Reading
- HMAC Request Signing -- Full signing spec with code examples in Python, Node.js, Go, PHP, and cURL
- Webhooks -- Receive signed webhook deliveries and verify signatures
- Security Considerations -- Key rotation, credential storage, network security