Back

Best Practices

Security, performance, and integration tips for production deployments

Best Practices

Follow these guidelines to build secure, performant, and maintainable integrations with the WalletWerk API.

Security

Never Commit API Keys

Never commit API keys to version control. Store API keys in environment variables or use secret management services in production.

# ✅ Good: Use environment variables
export WALLETWERK_API_KEY="wk_live_abc123..."

# ❌ Bad: Hardcode in source files
const API_KEY = "wk_live_abc123...";

Use Descriptive Names for API Keys

Name your keys based on their purpose (e.g., "Production App", "Webhook Handler", "Development Testing"). This makes it easier to audit usage and rotate keys.

Rotate keys regularly as a security best practice. You can create new keys and revoke old ones from the dashboard.

Limit Scopes

Only grant the minimum scopes needed for each API key. Use read-only keys (campaigns:read) when possible. Separate keys for different services allow for better access control.

Available Scopes:

  • campaigns:read — Read campaigns and recipients
  • campaigns:write — Create and update campaigns and recipients
  • pkpass:verify — Verify Apple Wallet .pkpass files

Performance

Use Pagination

Always specify limit and offset when listing resources. Don't request more data than you need — smaller responses are faster and use less bandwidth.

// ✅ Good: Use pagination
const response = await fetch(
  `${BASE_URL}/campaigns?limit=20&offset=0`,
  { headers: { 'Authorization': `Bearer ${API_KEY}` } }
);

// ❌ Bad: Request all data at once
const response = await fetch(
  `${BASE_URL}/campaigns?limit=1000`,
  { headers: { 'Authorization': `Bearer ${API_KEY}` } }
);

Handle Rate Limits Gracefully

Monitor the rate limit headers in responses (X-RateLimit-Remaining, X-RateLimit-Reset). Implement exponential backoff for retries when you receive a 429 response:

async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const response = await fetch(url, options);
    
    if (response.status === 429) {
      const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
      await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
      continue;
    }
    
    return response;
  }
  throw new Error('Max retries exceeded');
}

Cache When Appropriate

Cache recipient and campaign data on your end when possible. Don't repeatedly fetch the same wallet card information — use webhooks instead to receive updates.

Webhooks provide real-time updates without polling. Set up webhooks to receive notifications when cards become active, removed, or updated.

Error Handling

Check Response Status Codes

  • 4xx errors — Client errors that require fixing your request (bad input, missing fields, invalid auth)
  • 5xx errors — Server errors that may be transient; implement retry with exponential backoff
const response = await fetch(url, options);

if (!response.ok) {
  if (response.status >= 400 && response.status < 500) {
    // Client error - fix your request
    const error = await response.json();
    console.error('Client error:', error);
  } else if (response.status >= 500) {
    // Server error - retry with backoff
    // ... retry logic
  }
}

Log Errors for Debugging

Include relevant context in error logs (endpoint, timestamp, error code). Never log API keys — they should remain secret even in logs.

Validate Input Before Sending

  • Check required fields before making requests
  • Validate email formats
  • Ensure dates are in ISO 8601 format (YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ)
function validateRecipient(recipient) {
  if (!recipient.email || !isValidEmail(recipient.email)) {
    throw new Error('Invalid email address');
  }
  // ... more validation
}

Integration Tips

Use External IDs

Store your internal user/customer IDs in the externalId field when creating recipients. This makes it much easier to sync data between WalletWerk and your systems.

{
  email: 'user@example.com',
  name: 'John Doe',
  externalId: 'user_12345'  // Your internal user ID
}

Set Up Webhooks

Don't poll the API for status changes. Instead, configure webhook endpoints to receive real-time notifications when cards become active, removed, or updated. Always verify webhook signatures using your signing secret.

See the Webhooks section for implementation details.

Test Your Integration Thoroughly

  • Start with test API keys and verify behavior before using production keys
  • Monitor usage and errors in the dashboard
  • Test the complete flow: create campaign → add recipient → invite → install → update → revoke

Use the dashboard's test webhook feature to verify your webhook endpoint before going live.

Real-time Updates

When you modify a campaign or recipient, changes automatically propagate to all active cards:

  • Campaign updates (name, venue, dates) → All cards in the campaign
  • Recipient updates (name, data) → That recipient's card only

For Apple Wallet, updates sync on the next wallet sync (usually within minutes). For Google Wallet, updates are immediate.

Idempotency

For bulk operations, use the X-WalletWerk-Delivery-ID header from webhooks to ensure idempotent processing. Store processed delivery IDs to prevent duplicate processing.

See the Webhooks section for more details.