Skip to main content

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

  1. Go to Partner Portal
  2. Navigate to Settings → Security
  3. Click Generate New Key
  4. 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:

HeaderDescription
X-Loyalteez-Brand-IdYour Brand ID (wallet address)
X-Loyalteez-SignatureHMAC-SHA256 signature
X-Loyalteez-TimestampUnix timestamp in milliseconds
Content-Typeapplication/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"

  1. Check timestamp — Must be within 5 minutes
  2. Verify body — JSON must match exactly (no extra whitespace)
  3. Check key — Security key must be correct
  4. Check ordertimestamp: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

  1. Store keys securely — Use environment variables or secrets manager
  2. Rotate keys periodically — Generate new keys in Partner Portal
  3. Use HTTPS only — Never send requests over HTTP
  4. Validate responses — Check for error codes
  5. Log for debugging — Keep audit trail (excluding keys)