UGCDocumentation

Security Best Practices

Best practices for securing your UGC API keys and tokens

Updated July 14, 2026

Securing your UGC API keys and tokens is crucial to protect your business and customer data. Follow these best practices to ensure the security of your API credentials.

General Security Tips

Use HTTPS Everywhere

Always use HTTPS to protect data in transit:

javascript
//  Good - HTTPS
const apiUrl = 'https://api.ugc.africa';

// ❌ Bad - HTTP (insecure)
const apiUrl = 'http://api.example.com';

Environment Variables

Store all credentials in environment variables:

Bash
# .env file
UGC_API_KEY=UGSK_live_your_key_here
UGC_WEBHOOK_SECRET=whsec_your_secret_here

Use .gitignore

Always ignore sensitive files in version control:

bash
# .gitignore
.env
.env.local
.env.production
*.key
*.pem
secrets/

Regular Key Rotation

Rotate API keys every 90 days:

javascript
// Automated key rotation reminder
const KEY_ROTATION_DAYS = 90;
const lastRotation = new Date('2024-01-01');
const daysSince = (Date.now() - lastRotation) / (1000 * 60 * 60 * 24);

if (daysSince > KEY_ROTATION_DAYS) {
  console.warn('⚠ API keys should be rotated!');
}

API Key Security

Server-Side Only

API keys must only be used server-side:

javascript
// Good – server-side only (e.g. Node.js + Express)
const express = require('express');
const app = express();

app.post('/create-campaign', express.json(), async (req, res) => {
  const r = await fetch('https://api.ugc.africa/automation/actions/campaigns', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.UGSK_LIVE}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(req.body),
  });
  const json = await r.json();
  res.status(r.status).json(json);
});

// ❌ Bad – never call the API with your API key from the browser

One key per integration

Create a separate key per service or environment in Developer Console so you can rotate or revoke one integration's access without affecting the others:

javascript
const campaignsKey = process.env.UGSK_CAMPAIGNS;

await fetch('https://api.ugc.africa/automation/actions/campaigns', {
  headers: { Authorization: `Bearer ${campaignsKey}` },
});

Monitor Usage

Track API key usage in your dashboard:

javascript
// Example: thin wrapper so you can audit calls to UGC
async function UGCApiFetch(url, options = {}) {
  const res = await fetch(url, options);
  console.log(
    JSON.stringify({ url, status: res.status, at: new Date().toISOString() }),
  );
  return res;
}

Revoke Compromised Keys

Immediately revoke keys if compromised:

Revoke or rotate keys immediately in Developer Console if you suspect exposure. Do not wait for the next deploy.

JWT Token Security

The patterns below apply when your application issues JWTs for your own users. Validate exp, iss, and aud the way your identity provider recommends. For people using UGC in the browser, see .

Short Expiration Times

Use short-lived tokens (1 hour):

javascript
// Token expires in 1 hour
const token = jwt.sign(payload, secret, { expiresIn: '1h' });

Secure Storage

Store JWT tokens securely:

javascript
//  Good - httpOnly cookie (server-side)
res.cookie('token', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  maxAge: 3600000 // 1 hour
});

//  Good - Secure storage (mobile)
await SecureStore.setItemAsync('UGC_token', token);

// ❌ Bad - localStorage (vulnerable to XSS)
localStorage.setItem('token', token);

Implement Token Refresh

Always implement token refresh:

javascript
let accessToken = null;
let refreshToken = null;

async function makeRequest(url) {
  let res = await fetch(url, {
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  if (res.status === 401 && refreshToken) {
    // Pseudocode: exchange refresh for new access token (your auth server / IdP)
    const refreshed = await fetch('https://your-idp.example/oauth/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ grant_type: 'refresh_token', refresh_token: refreshToken }),
    });
    const body = await refreshed.json();
    accessToken = body.access_token;
    refreshToken = body.refresh_token ?? refreshToken;
    res = await fetch(url, {
      headers: { Authorization: `Bearer ${accessToken}` },
    });
  }
  return res;
}

Validate Tokens

Always validate JWT tokens:

javascript
const jwt = require('jsonwebtoken');

function validateToken(token) {
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    
    // Check expiration
    if (decoded.exp < Date.now() / 1000) {
      throw new Error('Token expired');
    }
    
    // Check issuer (use your service’s expected `iss`, or what UGC documents for a given integration)
    if (decoded.iss !== 'https://your-idp.example') {
      throw new Error('Invalid issuer');
    }
    
    return decoded;
  } catch (error) {
    console.error('Token validation failed:', error);
    return null;
  }
}

Webhook Security

Verify Signatures

Always verify webhook signatures:

JavaScript
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret) {
  const hmac = crypto.createHmac('sha256', secret);
  const digest = hmac.update(payload).digest('hex');
  
  // Constant-time comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(digest)
  );
}

app.post('/webhooks/UGC', express.raw({type: 'application/json'}), (req, res) => {
  const signature = req.headers['x-ugc-signature'];
  const isValid = verifyWebhookSignature(
    req.body,
    signature,
    process.env.UGC_WEBHOOK_SECRET
  );
  
  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process webhook
  const event = JSON.parse(req.body);
  handleWebhookEvent(event);
  
  res.status(200).send('OK');
});

Use HTTPS for Webhooks

Always use HTTPS URLs for webhook endpoints:

bash
#  Good
https://api.yoursite.com/webhooks/UGC

# ❌ Bad
http://api.yoursite.com/webhooks/UGC

Implement Idempotency

Handle duplicate webhooks:

javascript
const processedEvents = new Set();

function handleWebhookEvent(event) {
  // Check if already processed
  if (processedEvents.has(event.id)) {
    console.log('Event already processed:', event.id);
    return;
  }
  
  // Process event (see /api/automation/triggers for the full trigger catalog)
  switch (event.trigger) {
    case 'campaign_funded':
      handleCampaignFunded(event.data);
      break;
    case 'earnings_released':
      handleEarningsReleased(event.data);
      break;
  }
  
  // Mark as processed
  processedEvents.add(event.id);
}

Rate Limiting

Implement rate limiting to prevent abuse:

javascript
const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: 'Too many requests, please try again later'
});

app.use('/api/', apiLimiter);

Data Encryption

Encrypt Sensitive Data

Encrypt sensitive data at rest:

javascript
const crypto = require('crypto');

function encrypt(text, key) {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
  let encrypted = cipher.update(text);
  encrypted = Buffer.concat([encrypted, cipher.final()]);
  return iv.toString('hex') + ':' + encrypted.toString('hex');
}

function decrypt(text, key) {
  const parts = text.split(':');
  const iv = Buffer.from(parts.shift(), 'hex');
  const encrypted = Buffer.from(parts.join(':'), 'hex');
  const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv);
  let decrypted = decipher.update(encrypted);
  decrypted = Buffer.concat([decrypted, decipher.final()]);
  return decrypted.toString();
}

// Encrypt customer data
const encryptedEmail = encrypt(customerEmail, process.env.ENCRYPTION_KEY);

Use TLS 1.2+

Ensure your server uses TLS 1.2 or higher:

javascript
const https = require('https');
const fs = require('fs');

const options = {
  key: fs.readFileSync('private-key.pem'),
  cert: fs.readFileSync('certificate.pem'),
  minVersion: 'TLSv1.2'
};

https.createServer(options, app).listen(443);

Logging and Monitoring

Log Security Events

Log all authentication attempts:

javascript
function logSecurityEvent(event) {
  console.log(JSON.stringify({
    timestamp: new Date().toISOString(),
    type: event.type,
    userId: event.userId,
    ipAddress: event.ipAddress,
    userAgent: event.userAgent,
    success: event.success
  }));
}

// Example only: authenticate against YOUR identity layer, then log
app.post('/auth/login', async (req, res) => {
  try {
    const session = await yourAuthService.authenticate(req.body);
    logSecurityEvent({
      type: 'login',
      userId: session.user.id,
      ipAddress: req.ip,
      userAgent: req.get('user-agent'),
      success: true
    });
    res.json(session);
  } catch (error) {
    logSecurityEvent({
      type: 'login_failed',
      userId: req.body.email,
      ipAddress: req.ip,
      userAgent: req.get('user-agent'),
      success: false
    });
    res.status(401).json({ error: 'Invalid credentials' });
  }
});

Monitor for Suspicious Activity

Set up alerts for unusual patterns:

javascript
const suspiciousPatterns = {
  maxFailedLogins: 5,
  maxRequestsPerMinute: 100,
  unusualLocations: ['CN', 'RU', 'KP'] // Example
};

function checkSuspiciousActivity(userId, activity) {
  if (activity.failedLogins > suspiciousPatterns.maxFailedLogins) {
    alert(`User ${userId} has ${activity.failedLogins} failed logins`);
    lockAccount(userId);
  }
  
  if (activity.requestsPerMinute > suspiciousPatterns.maxRequestsPerMinute) {
    alert(`User ${userId} is making ${activity.requestsPerMinute} requests/min`);
    temporarilyBlockUser(userId);
  }
}

Compliance

GDPR Compliance

Implement data protection measures:

javascript
// Data retention policy
const DATA_RETENTION_DAYS = 90;

async function deleteOldData() {
  const cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - DATA_RETENTION_DAYS);
  
  await db.logs.deleteMany({
    createdAt: { $lt: cutoffDate }
  });
}

// Right to be forgotten
async function deleteUserData(userId) {
  await db.users.deleteOne({ id: userId });
  await db.transactions.updateMany(
    { userId },
    { $set: { userId: 'deleted', email: '[email protected]' } }
  );
}

PCI DSS Compliance

Never store sensitive card data:

javascript
// ❌ Never do this
const unsafe = {
  cardNumber: '4111111111111111',
  cvv: '123',
  expiryDate: '12/25'
};

// ✅ Fund campaigns server-side; card data is entered on the hosted checkout page, never on your server
await fetch('https://api.ugc.africa/automation/actions/campaigns/cmp_123/fund', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.UGSK_LIVE}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ method: 'checkout', amount: 200 }),
});

Security Checklist

Before going live, ensure you've:

  • Moved all API keys to environment variables
  • Enabled HTTPS on all endpoints
  • Implemented webhook signature verification
  • Set up proper error handling (no sensitive data in errors)
  • Configured rate limiting
  • Implemented logging and monitoring
  • Set up automated key rotation reminders
  • Configured secure cookie settings
  • Enabled CSRF protection
  • Implemented token refresh logic
  • Set up security alerts
  • Reviewed and tested authentication flows
  • Configured proper CORS policies
  • Implemented account lockout after failed logins
  • Set up regular security audits

Resources

Incident Response

If you suspect a security breach:

1. Immediately revoke all potentially compromised API keys via the Developer Console

2. Rotate all credentials

3. Notify UGC security at [email protected]

4. Investigate access logs for unauthorized activity

5. Document the incident

6. Update security measures to prevent recurrence

Contact the security team: [email protected]

Did this solve your problem?