Authentication Guide
Secure your server-to-server integration with HMAC signature authentication.
Overview
The Loyalteez API uses HMAC-SHA256 signatures to verify requests come from your server. This prevents:
- Replay attacks (timestamp validation)
- Request tampering (payload signing)
- Unauthorized access (key verification)
Generate Your Security Key
- Go to Partner Portal
- Navigate to Settings → Security
- Click Generate New Key
- Copy and store securely — this is shown once
Keep Your Key Secret
Your Security Key is like a password. Never expose it in:
- Frontend code
- Public repositories
- Client-side applications
Request Headers
Every API request requires these headers:
| Header | Description |
|---|---|
X-Loyalteez-Brand-Id | Your Brand ID (wallet address) |
X-Loyalteez-Signature | HMAC-SHA256 signature |
X-Loyalteez-Timestamp | Unix timestamp in milliseconds |
Content-Type | application/json |
Signature Generation
Step 1: Create the Signing String
Concatenate:
timestamp + ":" + brandId + ":" + requestBody
Example:
1705862400000:0xYourBrandId:{"brandId":"0xYourBrandId","eventType":"quest_completed","userEmail":"[email protected]"}
Step 2: Generate HMAC-SHA256
Sign the string with your Security Key:
const crypto = require('crypto');
const signature = crypto
.createHmac('sha256', securityKey)
.update(signingString)
.digest('hex');
Step 3: Include in Request
curl -X POST https://api.loyalteez.app/loyalteez-api/manual-event \
-H "Content-Type: application/json" \
-H "X-Loyalteez-Brand-Id: 0xYourBrandId" \
-H "X-Loyalteez-Signature: abc123..." \
-H "X-Loyalteez-Timestamp: 1705862400000" \
-d '{"brandId":"0xYourBrandId","eventType":"quest_completed",...}'
Timestamp Validation
Requests are rejected if the timestamp is:
- More than 5 minutes old
- In the future
Always use current time:
const timestamp = Date.now();
Complete Implementation
Node.js
const crypto = require('crypto');
const fetch = require('node-fetch');
async function trackEvent(eventType, userEmail, metadata = {}) {
const brandId = process.env.LOYALTEEZ_BRAND_ID;
const securityKey = process.env.LOYALTEEZ_SECURITY_KEY;
const timestamp = Date.now();
const body = JSON.stringify({
brandId,
eventType,
userEmail,
metadata
});
const signingString = `${timestamp}:${brandId}:${body}`;
const signature = crypto
.createHmac('sha256', securityKey)
.update(signingString)
.digest('hex');
const response = await fetch('https://api.loyalteez.app/loyalteez-api/manual-event', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Loyalteez-Brand-Id': brandId,
'X-Loyalteez-Signature': signature,
'X-Loyalteez-Timestamp': timestamp.toString()
},
body
});
return response.json();
}
// Usage
const result = await trackEvent(
'quest_completed',
'[email protected]',
{ quest_name: 'First Blood' }
);
Python
import hmac
import hashlib
import time
import json
import requests
def track_event(event_type, user_email, metadata=None):
brand_id = os.environ['LOYALTEEZ_BRAND_ID']
security_key = os.environ['LOYALTEEZ_SECURITY_KEY']
timestamp = str(int(time.time() * 1000))
body = json.dumps({
'brandId': brand_id,
'eventType': event_type,
'userEmail': user_email,
'metadata': metadata or {}
})
signing_string = f"{timestamp}:{brand_id}:{body}"
signature = hmac.new(
security_key.encode(),
signing_string.encode(),
hashlib.sha256
).hexdigest()
response = requests.post(
'https://api.loyalteez.app/loyalteez-api/manual-event',
headers={
'Content-Type': 'application/json',
'X-Loyalteez-Brand-Id': brand_id,
'X-Loyalteez-Signature': signature,
'X-Loyalteez-Timestamp': timestamp
},
data=body
)
return response.json()
# Usage
result = track_event(
'quest_completed',
'[email protected]',
{'quest_name': 'First Blood'}
)
Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func trackEvent(eventType, userEmail string, metadata map[string]interface{}) (map[string]interface{}, error) {
brandId := os.Getenv("LOYALTEEZ_BRAND_ID")
securityKey := os.Getenv("LOYALTEEZ_SECURITY_KEY")
timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
body := map[string]interface{}{
"brandId": brandId,
"eventType": eventType,
"userEmail": userEmail,
"metadata": metadata,
}
bodyJSON, _ := json.Marshal(body)
signingString := fmt.Sprintf("%s:%s:%s", timestamp, brandId, string(bodyJSON))
h := hmac.New(sha256.New, []byte(securityKey))
h.Write([]byte(signingString))
signature := hex.EncodeToString(h.Sum(nil))
req, _ := http.NewRequest("POST",
"https://api.loyalteez.app/loyalteez-api/manual-event",
strings.NewReader(string(bodyJSON)))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Loyalteez-Brand-Id", brandId)
req.Header.Set("X-Loyalteez-Signature", signature)
req.Header.Set("X-Loyalteez-Timestamp", timestamp)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
return result, nil
}
Troubleshooting
"Invalid signature"
- Check timestamp — Must be within 5 minutes
- Verify body — JSON must match exactly (no extra whitespace)
- Check key — Security key must be correct
- Check order —
timestamp:brandId:body
"Timestamp expired"
Use current time: Date.now() (JavaScript) or int(time.time() * 1000) (Python)
"Brand not found"
Verify your Brand ID is correct (42 characters, starts with 0x)
Security Best Practices
- Store keys securely — Use environment variables or secrets manager
- Rotate keys periodically — Generate new keys in Partner Portal
- Use HTTPS only — Never send requests over HTTP
- Validate responses — Check for error codes
- Log for debugging — Keep audit trail (excluding keys)