Troubleshooting Guide

Solutions to common issues when integrating with Sales Webhooks.

🔧 Quick Diagnostics

Common Issues

Authentication Issues

🔴 Invalid API Key Error

Getting "Invalid API key" or 401 Unauthorized errors.

Symptoms:

{
  "error": {
    "type": "AUTHENTICATION_FAILED",
    "message": "Invalid API key"
  }
}

Solutions:

  1. Verify you're using the correct API key format: lwa.sk_live_...
  2. Check you're not using a test key (sk_test_) in production
  3. Ensure the API key hasn't been revoked
  4. Confirm you're setting the header correctly: X-API-Key: your_key

Debug with cURL:

curl -i https://api.saleswebhooks.com/api/v1/account \
  -H "X-API-Key: lwa.sk_live_your_key_here"

Webhook Delivery Issues

🔴 Webhooks Not Being Received

Your endpoint isn't receiving webhook events.

Checklist:

  1. Webhook configured? Check in Console → Settings → Webhooks
  2. Endpoint accessible? Must be HTTPS with valid SSL certificate
  3. Firewall rules? Whitelist Sales Webhooks IPs: 52.14.123.0/24
  4. Response time? Must respond within 10 seconds
  5. Response code? Must return 2xx status code

Test your endpoint:

# Send test webhook from Console
curl -X POST https://api.saleswebhooks.com/api/v1/webhooks/test \
  -H "X-API-Key: your_key" \
  -H "Content-Type: application/json"

Common endpoint issues:

  • Using HTTP instead of HTTPS
  • Self-signed SSL certificates
  • Basic auth blocking the request
  • Content-Type validation rejecting JSON

🔴 Webhook Signature Verification Failing

Unable to verify webhook signatures.

Common causes:

  1. Wrong secret: Ensure using the secret from webhook configuration
  2. Body parsing: Use raw body, not parsed JSON
  3. Encoding issues: Ensure UTF-8 encoding
  4. Timing: Old webhooks may fail if verified too late

Correct implementation (Node.js):

// CORRECT: Use raw body
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const timestamp = req.headers['x-webhook-timestamp'];
  
  const payload = `${timestamp}.${req.body}`;
  const expectedSignature = crypto
    .createHmac('sha256', webhookSecret)
    .update(payload, 'utf8')
    .digest('hex');
  
  if (signature !== expectedSignature) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process webhook
  const event = JSON.parse(req.body);
  res.status(200).send('OK');
});

Subscription Issues

🔴 LinkedIn Profile Not Found

Getting errors when trying to subscribe to profiles.

Error:

{
  "error": {
    "type": "LINKEDIN_ERROR",
    "message": "LinkedIn profile is private or doesn't exist"
  }
}

Solutions:

  1. Check URL format: Must be full URL like https://linkedin.com/in/username
  2. Profile visibility: Profile must be public
  3. Regional restrictions: Some profiles may be region-locked
  4. URL changes: User may have changed their custom URL

Valid URL formats:

  • https://linkedin.com/in/john-doe
  • https://www.linkedin.com/in/john-doe
  • https://linkedin.com/company/acme-corp
  • john-doe (missing domain)
  • linkedin.com/in/john-doe (missing protocol)

🔴 Duplicate Subscription Error

Getting "subscription already exists" errors.

Solutions:

  1. Check existing: List subscriptions first to check if exists
  2. Use upsert pattern: Check then create if not exists
  3. Handle gracefully: Treat as success if already subscribed

Idempotent subscription pattern:

async function ensureSubscription(linkedinUrl) {
  // Check if already subscribed
  const existing = await client.subscriptions.list({
    linkedin_url: linkedinUrl
  });
  
  if (existing.data.length > 0) {
    console.log('Already subscribed');
    return existing.data[0];
  }
  
  // Create new subscription
  return await client.subscriptions.create({
    entity_type: 'contact',
    linkedin_url: linkedinUrl
  });
}

Rate Limiting Issues

🔴 Rate Limit Exceeded

Getting 429 Too Many Requests errors.

Understanding rate limits:

  • Daily limits: Based on your plan (1K-50K requests)
  • Burst limits: 10-50 requests per second
  • Concurrent limits: 5-25 simultaneous requests

Best practices:

  1. Check headers: Monitor X-RateLimit-Remaining
  2. Implement backoff: Exponential backoff on 429s
  3. Batch operations: Use bulk endpoints when available
  4. Cache responses: Avoid redundant API calls

Rate limit handler:

async function apiCallWithRateLimit(fn) {
  const maxRetries = 3;
  let lastError;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fn();
      
      // Log remaining rate limit
      const remaining = response.headers['x-ratelimit-remaining'];
      console.log(`Rate limit remaining: ${remaining}`);
      
      return response;
    } catch (error) {
      if (error.status === 429) {
        const retryAfter = error.headers['retry-after'] || 60;
        console.log(`Rate limited. Waiting ${retryAfter}s...`);
        await sleep(retryAfter * 1000);
        lastError = error;
      } else {
        throw error;
      }
    }
  }
  
  throw lastError;
}

Performance Issues

🔴 Slow API Response Times

API calls taking longer than expected.

Optimization strategies:

  1. Use pagination: Limit results to what you need
  2. Filter server-side: Use query parameters to filter
  3. Parallel requests: Make independent calls concurrently
  4. Regional endpoints: Use closest API region

Efficient data fetching:

// Inefficient: Fetching all then filtering
const allSubs = await client.subscriptions.list({ limit: 1000 });
const activeSubs = allSubs.data.filter(s => s.status === 'active');

// Efficient: Filter server-side
const activeSubs = await client.subscriptions.list({
  status: 'active',
  limit: 100
});

// Parallel fetching for multiple resources
const [contacts, companies] = await Promise.all([
  client.subscriptions.list({ entity_type: 'contact' }),
  client.subscriptions.list({ entity_type: 'company' })
]);

Debug Tools & Techniques

🛠️ Debugging Toolkit

1. Request Bin for Webhooks

Use webhook.site to inspect webhook payloads:

  1. Go to webhook.site
  2. Copy your unique URL
  3. Configure it as your webhook endpoint
  4. Trigger test webhooks to see full payloads

2. API Explorer

Test API calls directly in Console:

Console → API Explorer → Try endpoints with your real data

3. Webhook Logs

View delivery attempts and responses:

Console → Webhooks → Webhook Delivery History

4. cURL Commands

Test raw API calls:

# Test authentication
curl -i https://api.saleswebhooks.com/api/v1/account \
  -H "X-API-Key: your_key"

# Test webhook endpoint
curl -X POST https://your-endpoint.com/webhook \
  -H "Content-Type: application/json" \
  -H "X-Webhook-Signature: test" \
  -d '{"test": true}'

Still Need Help?

📧 Contact Support

If you're still experiencing issues, our support team is here to help.

When contacting support, include:

  • Your account email
  • Request IDs from error responses
  • Complete error messages
  • Code snippets (sanitized of secrets)
  • Timeline of when issue started

Common Integration Patterns

Resilient Webhook Handler

// Production-ready webhook handler
class WebhookHandler {
  constructor(secret) {
    this.secret = secret;
    this.processedEvents = new Set();
  }
  
  async handle(req, res) {
    try {
      // 1. Verify signature
      if (!this.verifySignature(req)) {
        return res.status(401).json({ error: 'Invalid signature' });
      }
      
      // 2. Parse event
      const event = JSON.parse(req.body);
      
      // 3. Check for duplicate
      if (this.processedEvents.has(event.id)) {
        return res.status(200).json({ status: 'already_processed' });
      }
      
      // 4. Return immediately
      res.status(200).json({ status: 'accepted' });
      
      // 5. Process async
      setImmediate(() => {
        this.processEvent(event).catch(error => {
          console.error('Event processing failed:', error);
          // Send to dead letter queue
          this.deadLetterQueue.push(event);
        });
      });
      
    } catch (error) {
      console.error('Webhook handler error:', error);
      res.status(500).json({ error: 'Internal server error' });
    }
  }
  
  verifySignature(req) {
    const signature = req.headers['x-webhook-signature'];
    const timestamp = req.headers['x-webhook-timestamp'];
    
    // Prevent replay attacks (5 minute window)
    const age = Date.now() - parseInt(timestamp);
    if (age > 300000) return false;
    
    const payload = `${timestamp}.${req.body}`;
    const expected = crypto
      .createHmac('sha256', this.secret)
      .update(payload, 'utf8')
      .digest('hex');
    
    return signature === expected;
  }
  
  async processEvent(event) {
    this.processedEvents.add(event.id);
    
    // Process based on type
    switch (event.type) {
      case 'contact.position_changed':
        await this.handleJobChange(event);
        break;
      // ... other event types
    }
    
    // Clean up old event IDs (keep last 10k)
    if (this.processedEvents.size > 10000) {
      const arr = Array.from(this.processedEvents);
      this.processedEvents = new Set(arr.slice(-10000));
    }
  }
}

Next Steps