Best Practices

Overview

Follow these best practices to build production-ready integrations that are secure, reliable, and performant.

  • Security: Protect credentials and sensitive data.
  • Error Handling: Handle failures gracefully with retries.
  • Performance: Optimize API usage and respect rate limits.
  • Reliability: Build resilient integrations that handle edge cases.

Authentication & Security

Credential Management

Don’t do this:

1const apiKey = "deel_live_abc123"; // ❌ Never hardcode

Do this instead:

1const apiKey = process.env.DEEL_API_KEY; // ✅ Use environment variables
  • Use environment variables or secure vault services
  • Never commit credentials to version control
  • Add .env to your .gitignore file

Recommended rotation schedule:

  • API tokens: Every 90 days
  • OAuth2 access tokens: Refresh proactively (every 25 days)
  • Immediately rotate if compromise is suspected

Why rotate?

  • Employees leave
  • Credentials can be accidentally exposed
  • Security vulnerabilities can be discovered

Only request the scopes and permissions you absolutely need:

Too broad:

1// Requesting all possible scopes ❌
2scopes: "contracts:read contracts:write people:read people:write ..."

Appropriate:

1// Only what you need ✅
2scopes: "contracts:read" // Just reading contracts
  • Create separate tokens for different integrations
  • Review and remove unused scopes
  • Use organization tokens only when necessary

All API requests must use HTTPS:

Incorrect:

http://api.letsdeel.com/rest/v2/contracts ❌

Correct:

https://api.letsdeel.com/rest/v2/contracts ✅
  • HTTPS encrypts data in transit
  • Protects against man-in-the-middle attacks
  • Required by Deel API (HTTP requests will fail)

Webhook Security

When receiving webhooks, always verify the signature:

1const crypto = require('crypto');
2
3function verifyWebhookSignature(payload, signature, secret) {
4 const hash = crypto
5 .createHmac('sha256', secret)
6 .update(payload)
7 .digest('hex');
8
9 return crypto.timingSafeEqual(
10 Buffer.from(signature),
11 Buffer.from(hash)
12 );
13}
14
15// In your webhook handler
16app.post('/webhooks/deel', (req, res) => {
17 const signature = req.headers['x-deel-signature'];
18 const payload = JSON.stringify(req.body);
19
20 if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
21 return res.status(401).send('Invalid signature');
22 }
23
24 // Process webhook...
25});

Error Handling & Retries

Implement Exponential Backoff

Retry failed requests with exponential backoff to avoid overwhelming the API:

1async function makeRequestWithRetry(url, options, maxRetries = 3) {
2 for (let attempt = 0; attempt < maxRetries; attempt++) {
3 try {
4 const response = await fetch(url, options);
5
6 // Success
7 if (response.ok) {
8 return await response.json();
9 }
10
11 // Don't retry client errors (4xx except 429)
12 if (response.status >= 400 && response.status < 500 && response.status !== 429) {
13 throw new Error(`Client error: ${response.status}`);
14 }
15
16 // Retry on server errors (5xx) or rate limits (429)
17 if (attempt < maxRetries - 1) {
18 const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
19 await new Promise(resolve => setTimeout(resolve, delay));
20 continue;
21 }
22
23 throw new Error(`Request failed after ${maxRetries} attempts`);
24
25 } catch (error) {
26 if (attempt === maxRetries - 1) throw error;
27
28 const delay = Math.pow(2, attempt) * 1000;
29 await new Promise(resolve => setTimeout(resolve, delay));
30 }
31 }
32}

Handle Specific Error Codes

Different errors require different handling strategies:

Status CodeMeaningRecommended Action
400Bad RequestFix request parameters, don’t retry
401UnauthorizedCheck/refresh token, retry once
403ForbiddenCheck scopes, don’t retry
404Not FoundResource doesn’t exist, don’t retry
429Rate LimitedWait and retry with exponential backoff
500Server ErrorRetry with exponential backoff
503Service UnavailableRetry with exponential backoff

Validate Request Data

Always validate data before sending to the API:

1function validateContractData(data) {
2 const errors = [];
3
4 if (!data.client_id) {
5 errors.push('client_id is required');
6 }
7
8 if (!data.worker_email || !isValidEmail(data.worker_email)) {
9 errors.push('Valid worker_email is required');
10 }
11
12 if (data.rate && (data.rate <= 0 || data.rate > 10000)) {
13 errors.push('Rate must be between 0 and 10000');
14 }
15
16 if (errors.length > 0) {
17 throw new ValidationError(errors.join(', '));
18 }
19
20 return true;
21}
22
23// Use before making API call
24try {
25 validateContractData(contractData);
26 const response = await createContract(contractData);
27} catch (error) {
28 if (error instanceof ValidationError) {
29 // Handle validation error (don't send to API)
30 }
31}

Rate Limiting

Deel enforces a rate limit of 5 requests per second per organization. This limit is shared across all API tokens in your organization.

Important: Rate limits are organization-wide and Deel does not return rate limit headers. Proactive rate limiting through request queuing is essential.

Key Strategies

  • Request Queuing: Always implement request queuing to stay within limits.
  • Space Out Requests: Avoid bursts—spread requests over time.
  • Cache Responses: Reduce unnecessary API calls.
  • Centralize Requests: Coordinate all API calls across your organization.

Quick example:

1// Queue requests to respect rate limits
2const queue = new RateLimitedQueue(5); // 5 requests per second
3
4const result = await queue.add(() => deelAPI.get('/contracts'));
Learn More About Rate Limits

See the complete Rate Limits documentation for detailed strategies, code examples, and troubleshooting.

Idempotency

Use idempotency keys for POST and PATCH requests to safely retry without creating duplicates.

Idempotency keys prevent duplicate resources when retrying failed requests. Responses are cached for 24 hours.

Quick example:

1const { v4: uuidv4 } = require('uuid');
2
3async function createContract(contractData) {
4 const idempotencyKey = uuidv4();
5
6 return await deelAPI.post('/contracts', contractData, {
7 headers: {
8 'Idempotency-Key': idempotencyKey
9 }
10 });
11}

Key points:

  • Use UUID v4 for idempotency keys
  • Reuse the same key when retrying
  • Only successful responses (2xx) are cached
  • Keys are valid for 24 hours
Learn More About Idempotency

See the complete Idempotency documentation for detailed implementation, scenarios, and best practices.

Data Handling

Sanitize and Validate Input

Always validate and sanitize data from users:

1function sanitizeEmail(email) {
2 return email.trim().toLowerCase();
3}
4
5function validateContractInput(input) {
6 return {
7 worker_email: sanitizeEmail(input.worker_email),
8 job_title: input.job_title.trim().substring(0, 100), // Limit length
9 rate: Math.max(0, Number(input.rate)), // Ensure positive number
10 // ... other fields
11 };
12}

Always use UTC for dates and times:

1// Store dates in UTC
2const startDate = new Date().toISOString();
3
4// When displaying to users, convert to their timezone
5const userTimezone = 'America/New_York';
6const displayDate = new Intl.DateTimeFormat('en-US', {
7 timeZone: userTimezone,
8 dateStyle: 'full',
9 timeStyle: 'long'
10}).format(new Date(startDate));

For large datasets, use pagination properly:

1async function getAllContracts() {
2 let allContracts = [];
3 let page = 1;
4 let hasMore = true;
5
6 while (hasMore) {
7 const response = await deelAPI.get('/contracts', {
8 params: {
9 page,
10 limit: 100 // Max page size
11 }
12 });
13
14 allContracts = allContracts.concat(response.data);
15 hasMore = response.data.length === 100;
16 page++;
17
18 // Add small delay to avoid rate limits
19 if (hasMore) await sleep(100);
20 }
21
22 return allContracts;
23}

Testing

Test in Sandbox First

1

Use sandbox for development

Always develop and test against the sandbox environment:

1const baseURL = process.env.NODE_ENV === 'production'
2 ? 'https://api.letsdeel.com/rest/v2'
3 : 'https://api-sandbox.demo.deel.com/rest/v2';
2

Test error scenarios

Don’t just test happy paths. Test:

  • Invalid authentication
  • Missing required fields
  • Rate limit handling
  • Network failures
  • Webhook signature verification
3

Validate with production-like data

Use realistic test data that mirrors your production use case:

  • Multiple countries and currencies
  • Different contract types (EOR, IC, GP)
  • Edge cases (long names, special characters)
4

Gradual production rollout

When moving to production:

  • Start with a small subset of users/data
  • Monitor error rates and performance
  • Gradually increase usage
  • Keep sandbox testing environment available

Monitoring & Logging

Log Important Events

Implement structured logging for debugging and monitoring:

1const winston = require('winston');
2
3const logger = winston.createLogger({
4 level: 'info',
5 format: winston.format.json(),
6 transports: [
7 new winston.transports.File({ filename: 'error.log', level: 'error' }),
8 new winston.transports.File({ filename: 'combined.log' })
9 ]
10});
11
12// Log API calls
13logger.info('API Request', {
14 method: 'POST',
15 endpoint: '/contracts',
16 requestId: req.id,
17 timestamp: new Date().toISOString()
18});
19
20// Log errors with context
21logger.error('API Error', {
22 error: error.message,
23 endpoint: '/contracts',
24 statusCode: error.response?.status,
25 requestId: req.id
26});

What to log:

  • API request/response metadata (not full bodies with sensitive data)
  • Error conditions and stack traces
  • Authentication failures
  • Rate limit warnings
  • Webhook deliveries

What NOT to log:

  • API keys or tokens
  • Sensitive personal data
  • Full request/response bodies (unless sanitized)

Set Up Alerts

Monitor your integration health:

  • Error Rate Alerts: Alert when error rate exceeds threshold (e.g., >5% of requests)
  • Rate Limit Warnings: Alert when approaching rate limits (e.g., 80% usage)
  • Webhook Failures: Alert on webhook delivery failures or signature mismatches
  • Response Time: Alert on slow API responses (e.g., >2 seconds average)

Performance Optimization

Reuse HTTP connections for better performance:

1const axios = require('axios');
2const http = require('http');
3const https = require('https');
4
5const deelAPI = axios.create({
6 baseURL: 'https://api.letsdeel.com/rest/v2',
7 httpAgent: new http.Agent({ keepAlive: true }),
8 httpsAgent: new https.Agent({ keepAlive: true })
9});

Always set reasonable timeouts:

1const response = await deelAPI.get('/contracts', {
2 timeout: 10000 // 10 second timeout
3});

Only request the fields you need:

1// If API supports field selection
2const response = await deelAPI.get('/contracts', {
3 params: {
4 fields: 'id,status,worker_name,start_date' // Only needed fields
5 }
6});

When fetching multiple independent resources:

1// ❌ Sequential (slow)
2const contract = await getContract(contractId);
3const worker = await getWorker(workerId);
4const invoices = await getInvoices(contractId);
5
6// ✅ Concurrent (fast)
7const [contract, worker, invoices] = await Promise.all([
8 getContract(contractId),
9 getWorker(workerId),
10 getInvoices(contractId)
11]);

Compliance & Privacy

Personal Identifiable Information (PII) requires special care:

  • Only collect necessary PII
  • Encrypt PII at rest and in transit
  • Follow data retention policies
  • Implement right to deletion
  • Document data flows

Ensure compliance with relevant regulations:

  • GDPR (Europe): Data protection and privacy
  • CCPA (California): Consumer privacy rights
  • SOC 2: Information security standards
  • Industry-specific regulations

Maintain audit logs for compliance:

1function logAuditEvent(event) {
2 auditLogger.info({
3 timestamp: new Date().toISOString(),
4 userId: event.userId,
5 action: event.action, // 'contract_created', 'data_accessed', etc.
6 resourceId: event.resourceId,
7 ipAddress: event.ipAddress,
8 userAgent: event.userAgent
9 });
10}

Summary Checklist

Before deploying to production, ensure you’ve implemented:

  • API keys stored in environment variables
  • HTTPS used for all requests
  • Webhook signatures verified
  • Least privilege scopes requested
  • Credential rotation schedule in place
  • Secrets never committed to version control
  • Exponential backoff retry logic
  • Idempotency keys used for mutations
  • Error handling for all API calls
  • Request timeouts configured
  • Input validation implemented
  • Edge cases tested
  • Rate limits respected
  • Request queuing implemented
  • Appropriate caching strategy
  • Connection pooling enabled
  • Pagination handled efficiently
  • Concurrent requests where possible
  • Structured logging implemented
  • Error tracking configured
  • Rate limit monitoring
  • Alerts set up for failures
  • Webhook delivery monitoring
  • Performance metrics tracked

Next Steps