***

title: Best Practices
description: 'Build secure, reliable, and performant integrations with Deel APIs'
---------------------------------------------------------------------------------

## 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

<AccordionGroup>
  <Accordion title="Never hardcode credentials" icon="code">
    **Don't do this:**

    ```javascript
    const apiKey = "deel_live_abc123"; // ❌ Never hardcode
    ```

    **Do this instead:**

    ```javascript
    const 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
  </Accordion>

  <Accordion title="Rotate credentials regularly" icon="rotate">
    **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
  </Accordion>

  <Accordion title="Use the least privilege principle" icon="shield-halved">
    Only request the scopes and permissions you absolutely need:

    **Too broad:**

    ```javascript
    // Requesting all possible scopes ❌
    scopes: "contracts:read contracts:write people:read people:write ..."
    ```

    **Appropriate:**

    ```javascript
    // Only what you need ✅
    scopes: "contracts:read" // Just reading contracts
    ```

    * Create separate tokens for different integrations
    * Review and remove unused scopes
    * Use organization tokens only when necessary
  </Accordion>

  <Accordion title="Always use HTTPS" icon="lock">
    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)
  </Accordion>
</AccordionGroup>

### Webhook Security

When receiving webhooks, always verify the signature:

<CodeGroup>
  ```javascript Node.js
  const crypto = require('crypto');

  function verifyWebhookSignature(payload, signature, secret) {
    const hash = crypto
      .createHmac('sha256', secret)
      .update(payload)
      .digest('hex');

    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(hash)
    );
  }

  // In your webhook handler
  app.post('/webhooks/deel', (req, res) => {
    const signature = req.headers['x-deel-signature'];
    const payload = JSON.stringify(req.body);

    if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
      return res.status(401).send('Invalid signature');
    }

    // Process webhook...
  });
  ```

  ```python Python
  import hmac
  import hashlib

  def verify_webhook_signature(payload: str, signature: str, secret: str) -> bool:
      expected_signature = hmac.new(
          secret.encode(),
          payload.encode(),
          hashlib.sha256
      ).hexdigest()

      return hmac.compare_digest(signature, expected_signature)

  # In your webhook handler
  @app.route('/webhooks/deel', methods=['POST'])
  def handle_webhook():
      signature = request.headers.get('X-Deel-Signature')
      payload = request.get_data(as_text=True)

      if not verify_webhook_signature(payload, signature, os.getenv('WEBHOOK_SECRET')):
          return jsonify({'error': 'Invalid signature'}), 401

      # Process webhook...
  ```
</CodeGroup>

## Error Handling & Retries

### Implement Exponential Backoff

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

<CodeGroup>
  ```javascript Node.js
  async function makeRequestWithRetry(url, options, maxRetries = 3) {
    for (let attempt = 0; attempt < maxRetries; attempt++) {
      try {
        const response = await fetch(url, options);

        // Success
        if (response.ok) {
          return await response.json();
        }

        // Don't retry client errors (4xx except 429)
        if (response.status >= 400 && response.status < 500 && response.status !== 429) {
          throw new Error(`Client error: ${response.status}`);
        }

        // Retry on server errors (5xx) or rate limits (429)
        if (attempt < maxRetries - 1) {
          const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
          await new Promise(resolve => setTimeout(resolve, delay));
          continue;
        }

        throw new Error(`Request failed after ${maxRetries} attempts`);

      } catch (error) {
        if (attempt === maxRetries - 1) throw error;

        const delay = Math.pow(2, attempt) * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }
  ```

  ```python Python
  import time
  import requests
  from requests.adapters import HTTPAdapter
  from urllib3.util.retry import Retry

  def create_session_with_retries():
      session = requests.Session()

      retry_strategy = Retry(
          total=3,
          backoff_factor=1,  # 1s, 2s, 4s
          status_forcelist=[429, 500, 502, 503, 504],
          allowed_methods=["GET", "POST", "PUT", "PATCH", "DELETE"]
      )

      adapter = HTTPAdapter(max_retries=retry_strategy)
      session.mount("https://", adapter)

      return session

  # Usage
  session = create_session_with_retries()
  response = session.get(
      'https://api.letsdeel.com/rest/v2/contracts',
      headers={'Authorization': f'Bearer {api_key}'}
  )
  ```
</CodeGroup>

### Handle Specific Error Codes

Different errors require different handling strategies:

| Status Code | Meaning             | Recommended Action                      |
| ----------- | ------------------- | --------------------------------------- |
| `400`       | Bad Request         | Fix request parameters, don't retry     |
| `401`       | Unauthorized        | Check/refresh token, retry once         |
| `403`       | Forbidden           | Check scopes, don't retry               |
| `404`       | Not Found           | Resource doesn't exist, don't retry     |
| `429`       | Rate Limited        | Wait and retry with exponential backoff |
| `500`       | Server Error        | Retry with exponential backoff          |
| `503`       | Service Unavailable | Retry with exponential backoff          |

### Validate Request Data

Always validate data before sending to the API:

```javascript
function validateContractData(data) {
  const errors = [];

  if (!data.client_id) {
    errors.push('client_id is required');
  }

  if (!data.worker_email || !isValidEmail(data.worker_email)) {
    errors.push('Valid worker_email is required');
  }

  if (data.rate && (data.rate <= 0 || data.rate > 10000)) {
    errors.push('Rate must be between 0 and 10000');
  }

  if (errors.length > 0) {
    throw new ValidationError(errors.join(', '));
  }

  return true;
}

// Use before making API call
try {
  validateContractData(contractData);
  const response = await createContract(contractData);
} catch (error) {
  if (error instanceof ValidationError) {
    // Handle validation error (don't send to API)
  }
}
```

## 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.

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

### 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:**

```javascript
// Queue requests to respect rate limits
const queue = new RateLimitedQueue(5); // 5 requests per second

const result = await queue.add(() => deelAPI.get('/contracts'));
```

<Card title="Learn More About Rate Limits" icon="book" href="/api/rate-limits">
  See the complete [Rate Limits documentation](/api/rate-limits) for detailed strategies, code examples, and troubleshooting.
</Card>

## Idempotency

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

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

**Quick example:**

```javascript
const { v4: uuidv4 } = require('uuid');

async function createContract(contractData) {
  const idempotencyKey = uuidv4();

  return await deelAPI.post('/contracts', contractData, {
    headers: {
      'Idempotency-Key': idempotencyKey
    }
  });
}
```

**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

<Card title="Learn More About Idempotency" icon="book" href="/api/idempotency">
  See the complete [Idempotency documentation](/api/idempotency) for detailed implementation, scenarios, and best practices.
</Card>

## Data Handling

### Sanitize and Validate Input

<AccordionGroup>
  <Accordion title="Validate user input" icon="check">
    Always validate and sanitize data from users:

    ```javascript
    function sanitizeEmail(email) {
      return email.trim().toLowerCase();
    }

    function validateContractInput(input) {
      return {
        worker_email: sanitizeEmail(input.worker_email),
        job_title: input.job_title.trim().substring(0, 100), // Limit length
        rate: Math.max(0, Number(input.rate)), // Ensure positive number
        // ... other fields
      };
    }
    ```
  </Accordion>

  <Accordion title="Handle timezone conversions" icon="clock">
    Always use UTC for dates and times:

    ```javascript
    // Store dates in UTC
    const startDate = new Date().toISOString();

    // When displaying to users, convert to their timezone
    const userTimezone = 'America/New_York';
    const displayDate = new Intl.DateTimeFormat('en-US', {
      timeZone: userTimezone,
      dateStyle: 'full',
      timeStyle: 'long'
    }).format(new Date(startDate));
    ```
  </Accordion>

  <Accordion title="Handle pagination efficiently" icon="arrows-left-right-to-line">
    For large datasets, use pagination properly:

    ```javascript
    async function getAllContracts() {
      let allContracts = [];
      let page = 1;
      let hasMore = true;

      while (hasMore) {
        const response = await deelAPI.get('/contracts', {
          params: {
            page,
            limit: 100 // Max page size
          }
        });

        allContracts = allContracts.concat(response.data);
        hasMore = response.data.length === 100;
        page++;

        // Add small delay to avoid rate limits
        if (hasMore) await sleep(100);
      }

      return allContracts;
    }
    ```
  </Accordion>
</AccordionGroup>

## Testing

### Test in Sandbox First

<Steps>
  <Step title="Use sandbox for development">
    Always develop and test against the sandbox environment:

    ```javascript
    const baseURL = process.env.NODE_ENV === 'production'
      ? 'https://api.letsdeel.com/rest/v2'
      : 'https://api-sandbox.demo.deel.com/rest/v2';
    ```
  </Step>

  <Step title="Test error scenarios">
    Don't just test happy paths. Test:

    * Invalid authentication
    * Missing required fields
    * Rate limit handling
    * Network failures
    * Webhook signature verification
  </Step>

  <Step title="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)
  </Step>

  <Step title="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
  </Step>
</Steps>

## Monitoring & Logging

### Log Important Events

Implement structured logging for debugging and monitoring:

```javascript
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

// Log API calls
logger.info('API Request', {
  method: 'POST',
  endpoint: '/contracts',
  requestId: req.id,
  timestamp: new Date().toISOString()
});

// Log errors with context
logger.error('API Error', {
  error: error.message,
  endpoint: '/contracts',
  statusCode: error.response?.status,
  requestId: req.id
});
```

**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

<AccordionGroup>
  <Accordion title="Use connection pooling" icon="diagram-project">
    Reuse HTTP connections for better performance:

    ```javascript
    const axios = require('axios');
    const http = require('http');
    const https = require('https');

    const deelAPI = axios.create({
      baseURL: 'https://api.letsdeel.com/rest/v2',
      httpAgent: new http.Agent({ keepAlive: true }),
      httpsAgent: new https.Agent({ keepAlive: true })
    });
    ```
  </Accordion>

  <Accordion title="Implement request timeouts" icon="stopwatch">
    Always set reasonable timeouts:

    ```javascript
    const response = await deelAPI.get('/contracts', {
      timeout: 10000 // 10 second timeout
    });
    ```
  </Accordion>

  <Accordion title="Minimize payload size" icon="minimize">
    Only request the fields you need:

    ```javascript
    // If API supports field selection
    const response = await deelAPI.get('/contracts', {
      params: {
        fields: 'id,status,worker_name,start_date' // Only needed fields
      }
    });
    ```
  </Accordion>

  <Accordion title="Use async/concurrent requests" icon="bolt">
    When fetching multiple independent resources:

    ```javascript
    // ❌ Sequential (slow)
    const contract = await getContract(contractId);
    const worker = await getWorker(workerId);
    const invoices = await getInvoices(contractId);

    // ✅ Concurrent (fast)
    const [contract, worker, invoices] = await Promise.all([
      getContract(contractId),
      getWorker(workerId),
      getInvoices(contractId)
    ]);
    ```
  </Accordion>
</AccordionGroup>

## Compliance & Privacy

<AccordionGroup>
  <Accordion title="Handle PII appropriately" icon="user-shield">
    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
  </Accordion>

  <Accordion title="Comply with data regulations" icon="scale-balanced">
    Ensure compliance with relevant regulations:

    * **GDPR** (Europe): Data protection and privacy
    * **CCPA** (California): Consumer privacy rights
    * **SOC 2**: Information security standards
    * Industry-specific regulations
  </Accordion>

  <Accordion title="Audit trail" icon="list-check">
    Maintain audit logs for compliance:

    ```javascript
    function logAuditEvent(event) {
      auditLogger.info({
        timestamp: new Date().toISOString(),
        userId: event.userId,
        action: event.action, // 'contract_created', 'data_accessed', etc.
        resourceId: event.resourceId,
        ipAddress: event.ipAddress,
        userAgent: event.userAgent
      });
    }
    ```
  </Accordion>
</AccordionGroup>

## Summary Checklist

Before deploying to production, ensure you've implemented:

<Accordion title="Security Checklist" icon="shield-check">
  * [ ] 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
</Accordion>

<Accordion title="Reliability Checklist" icon="server">
  * [ ] Exponential backoff retry logic
  * [ ] Idempotency keys used for mutations
  * [ ] Error handling for all API calls
  * [ ] Request timeouts configured
  * [ ] Input validation implemented
  * [ ] Edge cases tested
</Accordion>

<Accordion title="Performance Checklist" icon="gauge">
  * [ ] Rate limits respected
  * [ ] Request queuing implemented
  * [ ] Appropriate caching strategy
  * [ ] Connection pooling enabled
  * [ ] Pagination handled efficiently
  * [ ] Concurrent requests where possible
</Accordion>

<Accordion title="Monitoring Checklist" icon="chart-line">
  * [ ] Structured logging implemented
  * [ ] Error tracking configured
  * [ ] Rate limit monitoring
  * [ ] Alerts set up for failures
  * [ ] Webhook delivery monitoring
  * [ ] Performance metrics tracked
</Accordion>

## Next Steps

<CardGroup cols={1}>
  <Card title="Webhooks" icon="fa-light webhook" href="/api/webhooks/quickstart">
    Set up webhook integrations
  </Card>
</CardGroup>
