Technical Troubleshooting
Diagnose and resolve technical issues with LocallyGrown integrations, APIs, webhooks, and applications. Find solutions to common problems and learn debugging techniques.
Common API Issues
🔐 Authentication Problems
401 Unauthorized Error
Symptoms: API requests return "Unauthorized" error
Common Causes:
- Invalid or expired API key
- Missing Authorization header
- Wrong API key format
- Using test key in production
Solutions:
- Verify API key is correct and active
- Check Authorization header format:
Bearer {api_key} - Ensure using live keys for production
- Regenerate API keys if compromised
403 Forbidden Error
Symptoms: Valid authentication but access denied
Common Causes:
- Insufficient permissions for API key
- Resource not accessible for your market
- Rate limit exceeded
- IP address restrictions
Solutions:
- Check API key permissions in admin panel
- Verify market ID in requests
- Implement rate limiting in your application
- Contact support for IP whitelist updates
📡 Connection Issues
Network Timeouts
Symptoms: Requests hang or timeout
Common Causes:
- Slow network connection
- Large payload sizes
- Server overload
- Firewall blocking requests
Solutions:
- Increase timeout values (max 30 seconds)
- Implement retry logic with exponential backoff
- Check firewall and proxy settings
- Use pagination for large data sets
SSL/TLS Certificate Errors
Symptoms: Certificate verification failures
Common Causes:
- Outdated certificate store
- Corporate firewall interference
- Self-signed certificates in development
- Clock synchronization issues
Solutions:
- Update system certificate store
- Configure proxy settings correctly
- Use proper certificates in all environments
- Synchronize system clock
🔧 Error Response Handling
Improved Error Responses
What's Changed: API endpoints now consistently return JSON error responses
Error Response Format:
{
"success": false,
"error": {
"message": "Human-readable error description",
"code": "ERROR_CODE",
"details": {
// Additional context when available
}
}
} Success responses use the mirror shape: { "success": true },
with an optional data payload and an optional message. The success flag is how client code distinguishes the two — check that
first, then read error.code for programmatic handling or error.message for the user-visible string.
Benefits:
- Consistent error format across all endpoints
- Machine-readable error codes for automated handling
- Helpful error messages for debugging
- No more HTML error pages in API responses
Example Error Handling:
try {
const response = await fetch('/api/orders', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify(orderData)
});
const data = await response.json();
if (!response.ok) {
// Handle API error
console.error(`API Error: ${data.error.message} (${data.error.code})`);
switch (data.error.code) {
case 'INVALID_PAYMENT':
handlePaymentError(data.error.details);
break;
case 'INSUFFICIENT_STOCK':
handleStockError(data.error.details);
break;
default:
showGenericError(data.error.message);
}
}
} catch (error) {
// Handle network or parsing errors
console.error('Request failed:', error);
}Webhook Troubleshooting
Webhooks Not Being Delivered
Diagnostic Steps:
- Check webhook URL accessibility from external networks
- Verify endpoint returns 200 status code
- Test webhook URL with curl or Postman
- Review webhook logs in admin dashboard
Testing Webhook Endpoint
# Test webhook endpoint accessibility
curl -X POST https://your-app.com/webhooks/locallygrown \
-H "Content-Type: application/json" \
-H "X-LocallyGrown-Signature: test" \
-d='{"test": true}'
# Expected response: 200 OK
# Check webhook logs
tail -f /var/log/your-app/webhooks.logSignature Verification Failures
Common Problems:
- Incorrect webhook secret
- Wrong payload encoding
- Modified request body
- Timing-based signature mismatches
Debug Signature Verification
// Log signature components for debugging
app.post('/webhook', (req, res) => {
const signature = req.headers['x-locallygrown-signature'];
const payload = JSON.stringify(req.body);
console.log('Received signature:', signature);
console.log('Payload length:', payload.length);
console.log('Payload preview:', payload.substring(0, 100));
// Your verification logic here
const isValid = verifySignature(payload, signature, webhookSecret);
console.log('Signature valid:', isValid);
if (!isValid) {
console.log('Expected signature would be:', generateSignature(payload, webhookSecret));
return res.status(401).send('Invalid signature');
}
res.status(200).send('OK');
});Duplicate Event Processing
Symptoms: Same event processed multiple times
Solutions:
- Implement idempotency using event IDs
- Use database constraints to prevent duplicates
- Respond with 200 for already processed events
- Implement proper error handling
Idempotency Implementation
const processedEvents = new Set();
app.post('/webhook', async (req, res) => {
const { id, type, data } = req.body;
// Check if event already processed
if (processedEvents.has(id) || await isEventInDatabase(id)) {
console.log(`Event ${id} already processed`);
return res.status(200).send('Already processed');
}
try {
await processEvent(type, data);
await saveEventToDatabase(id, type, data);
processedEvents.add(id);
res.status(200).send('OK');
} catch (error) {
console.error(`Failed to process event ${id}:`, error);
res.status(500).send('Processing failed');
}
});Payment Integration Issues
Payment Failures
Common Error Codes:
- card_declined - Card issuer declined the payment
- insufficient_funds - Not enough money in account
- invalid_card - Card number or details invalid
- expired_card - Card past expiration date
Debugging Steps:
- Check Stripe/payment processor logs
- Verify card details and test with known good card
- Test in sandbox mode first
- Review payment method configuration
Webhook Event Mismatches
Symptoms: Payment status in LocallyGrown doesn't match processor
Solutions:
- Verify webhook endpoints are configured correctly
- Check webhook event filtering
- Implement webhook retry handling
- Monitor webhook delivery logs
Integration Performance Issues
Rate Limiting Problems
429 Too Many Requests
Symptoms: API returns rate limit errors
Solutions:
- Implement exponential backoff retry logic
- Use batch operations where available
- Cache frequently accessed data
- Spread requests over time
Rate Limiting with Retry Logic
async function apiRequestWithRetry(url, options, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || Math.pow(2, attempt);
console.log(`Rate limited, retrying after ${retryAfter}s`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response;
} catch (error) {
if (attempt === maxRetries) {
throw error;
}
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
}
}
}Slow Response Times
Performance Optimization
- Use pagination - Request data in smaller chunks
- Implement caching - Cache static or slow-changing data
- Parallel requests - Make multiple API calls concurrently
- Filter data - Request only needed fields and records
Parallel API Requests
// Instead of sequential requests
const orders = await getOrders();
const customers = await getCustomers();
const products = await getProducts();
// Use parallel requests
const [orders, customers, products] = await Promise.all([
getOrders(),
getCustomers(),
getProducts()
]);
// With error handling
const results = await Promise.allSettled([
getOrders(),
getCustomers(),
getProducts()
]);
results.forEach((result, index) => {
if (result.status === 'rejected') {
console.error(`Request ${index} failed:`, result.reason);
}
});Debugging Tools and Techniques
API Request Debugging
curl Commands
Test API endpoints directly:
# Test authentication
curl -H "Authorization: Bearer sk_test_..." \
https://api.locallygrown.net/v1/orders
# Test with verbose output
curl -v -X POST \
-H "Authorization: Bearer sk_test_..." \
-H "Content-Type: application/json" \
-d='{"test": "data"}' \
https://api.locallygrown.net/v1/webhook-test
# Save response to file
curl -o response.json \
-H "Authorization: Bearer sk_test_..." \
https://api.locallygrown.net/v1/customersNetwork Monitoring
Monitor API traffic and responses:
- Browser DevTools - Network tab for web requests
- Wireshark - Detailed network packet analysis
- Charles Proxy - HTTP debugging proxy
- Postman - API testing and debugging
Logging Best Practices
What to Log
- Request details - Method, URL, headers (not auth tokens)
- Response status - Status codes and error messages
- Timing information - Request duration and timestamps
- User context - User ID, market ID, session info
Structured Logging Example
const logger = require('winston');
// Configure structured logging
const apiLogger = logger.createLogger({
format: logger.format.combine(
logger.format.timestamp(),
logger.format.json()
),
transports: [
new logger.transports.File({ filename: 'api.log' })
]
});
// Log API requests
function logApiRequest(req, res, responseTime) {
apiLogger.info({
type: 'api_request',
method: req.method,
url: req.url,
statusCode: res.statusCode,
responseTime: responseTime,
userAgent: req.headers['user-agent'],
userId: req.user?.id,
marketId: req.headers['x-market-id']
});
}
// Log errors with context
function logError(error, context) {
apiLogger.error({
type: 'error',
message: error.message,
stack: error.stack,
context: context,
timestamp: new Date().toISOString()
});
}Environment-Specific Issues
Development Environment
Common Issues:
- Using production API keys in development
- SSL certificate problems with localhost
- CORS issues with browser-based requests
- Environment variable configuration
Solutions:
- Use test API keys for development
- Configure local SSL or use tunneling tools (ngrok)
- Set up proper CORS headers
- Use .env files for configuration
Production Environment
Common Issues:
- Environment variable mismatches
- Network connectivity restrictions
- Load balancer configuration problems
- Database connection issues
Solutions:
- Validate environment configuration on deployment
- Configure firewall rules for API access
- Ensure load balancer passes headers correctly
- Implement connection pooling and health checks
Environment Configuration Check
// Validate environment configuration
function validateEnvironment() {
const required = [
'LOCALLYGROWN_API_KEY',
'LOCALLYGROWN_WEBHOOK_SECRET',
'DATABASE_URL',
'REDIS_URL'
];
const missing = required.filter(key => !process.env[key]);
if (missing.length > 0) {
console.error('Missing required environment variables:', missing);
process.exit(1);
}
// Validate API key format
const apiKey = process.env.LOCALLYGROWN_API_KEY;
if (!apiKey.startsWith('sk_')) {
console.error('Invalid API key format');
process.exit(1);
}
console.log('Environment configuration validated');
}
validateEnvironment();Error Monitoring and Alerting
Monitoring Setup
- Error tracking - Use Sentry, Bugsnag, or similar services
- API monitoring - Monitor endpoint availability and response times
- Log aggregation - Centralize logs with ELK stack or similar
- Metrics collection - Track business and technical metrics
Key Metrics to Monitor
API Health
- Response time percentiles
- Error rate by endpoint
- Rate limit utilization
- Authentication failures
Integration Health
- Webhook delivery success rate
- Data synchronization lag
- Failed payment processing
- Queue processing delays
Business Metrics
- Order processing time
- Customer signup rate
- Revenue tracking accuracy
- Inventory sync reliability
Built-in Health-Check Endpoints
LocallyGrown exposes three HTTP endpoints for uptime probes, load-balancer checks, and on-call dashboards. All three return JSON; the first is public, the other two are admin-gated in production.
GET /api/health
Auth: none. Suitable for external monitors (Pingdom, Better Uptime, Kubernetes liveness probes).
Runs parallel checks against the database, Redis, and Node heap memory, with a
5-second overall timeout. Returns a body with an overall status (healthy, degraded, unhealthy, not_initialized, or unknown), plus timestamp, version, uptime, responseTime, and a checks object covering each dependency.
HTTP status: 200 for healthy or degraded, 503 for unhealthy or not_initialized. A Cache-Control: no-cache header is set so intermediaries never stale-serve
the result. Append ?verbose=true for a more detailed body (connection counts, Redis memory string,
heap-limit byte counts).
HEAD /api/health is available for monitors that only need the status code
— it returns 200 if the simpler "can we reach DB and Redis?" probe
succeeds, 503 otherwise.
GET /api/health/db-pool
Auth: open in non-production; in production requires an authenticated
admin or superuser (401 otherwise).
Returns current MySQL pool stats: connectionsInUse, freeConnections, queuedRequests, maxConnections, and a computed usagePercent. The status field escalates from healthy → warning (>60% connection use or >5 queued requests) → critical (>80% use, >20 queued, or a suspected leak pattern).
Human-readable warnings and errors arrays explain why the state is what it is.
GET /api/health/queues
Auth: open in non-production; in production requires an authenticated admin or superuser.
Iterates the three BullMQ queues (emails, background tasks, Stripe webhooks) and
reports per-queue counts (waiting, active, completed, failed, delayed, paused), the lastJobTime, an approximate processingRate (jobs/min from the last 20 completions), and a rolling errorRate. The
overall status trips to warning when failures or waiting counts
build, and critical once error rates cross ~10% or failures exceed 100.
Use this endpoint as an input to alerts when a queue stops progressing, rather than watching the Bull dashboard by hand.
/api/health/db-pool and /api/health/queues leak enough shape
information about the deployment (pool sizing, queue depths, failure rates) that external
monitors should either authenticate as an admin, scrape from inside the network, or stick
to the public /api/health endpoint.Getting Additional Help
Support Resources
- Technical documentation - Complete API and integration guides
- Developer support - Email support for integration questions
- Community forums - Connect with other developers
- Status page - Real-time system status and incident updates
When Contacting Support
Include these details for faster resolution:
- Error messages - Complete error text and codes
- Request details - API endpoint, method, payload (sanitized)
- Response details - Status codes, headers, response body
- Environment info - Development vs production, library versions
- Steps to reproduce - Clear reproduction steps
- Expected vs actual behavior - What should happen vs what happens
Support Request Template
Subject: API Integration Issue - [Brief Description]
Environment: Production/Development
API Endpoint: GET /v1/orders
Error Code: 429
Response Time: 2024-01-15 10:30:00 UTC
Error Message:
{
"error": "rate_limit_exceeded",
"message": "Too many requests"
}
Request Details:
- Authorization: Bearer rk_live_xxx... (last 4 characters)
- Content-Type: application/json
- User-Agent: MyApp/1.0
Steps to Reproduce:
1. Make 100 API requests in quick succession
2. Error occurs on request #51
Expected Behavior:
Requests should be processed with appropriate rate limiting
Additional Context:
- This started happening after deploying version 2.1
- Same code works fine in development environment