Follow these guidelines to build integrations that are reliable, secure, and maintainable.
Architecture
Use webhooks instead of polling
Instead of repeatedly calling the API to check for changes, subscribe to webhook events. This is more efficient and gives you real-time updates.
Polling (bad): GET /contacts/ every 30 seconds
Webhooks (good): Receive ContactCreate event instantly
Implement idempotency
Design your handlers to be safe when called multiple times with the same data. Webhooks can be delivered more than once, and network issues may cause you to retry API calls.
// Use upsert instead of create to avoid duplicates
const response = await hoopai('/contacts/upsert', {
method: 'POST',
body: {
locationId: 'LOC_ID',
email: 'jane@example.com',
firstName: 'Jane',
lastName: 'Smith'
}
});
Handle pagination for large datasets
Never assume all results fit in a single response. Always paginate through results:
def get_all_contacts(api):
all_contacts = []
page = 1
while True:
result = api.get('/contacts/', limit=100, page=page)
all_contacts.extend(result['contacts'])
if not result['meta'].get('nextPage'):
break
page = result['meta']['nextPage']
return all_contacts
Error handling
Retry with exponential backoff
For transient errors (429, 500, 502, 503), retry with increasing delays:
| Attempt | Wait time |
|---|
| 1 | 1 second |
| 2 | 2 seconds |
| 3 | 4 seconds |
| 4 | 8 seconds |
| 5 | Give up, log, and alert |
Distinguish error types
| Error type | Status codes | Action |
|---|
| Client error | 400, 422 | Fix the request — don’t retry |
| Auth error | 401, 403 | Refresh token or re-authorize |
| Rate limit | 429 | Wait and retry using X-RateLimit-Reset |
| Server error | 5xx | Retry with backoff |
Security
Token management
- Never expose tokens in client-side code — always make API calls from your server
- Store tokens encrypted in your database or secrets manager
- Implement automatic token refresh before expiration, not after
- Rotate Private Integration keys periodically
Webhook security
- Always verify webhook signatures before processing payloads
- Validate the
locationId in webhook payloads matches a known installation
- Use HTTPS for all webhook endpoints
- Set a timeout — reject requests that take too long
Data handling
- Only request and store the data your integration needs
- Respect user privacy and comply with applicable regulations (GDPR, CCPA)
- Clean up stored data when your app is uninstalled
Batch operations
Where available, use bulk endpoints instead of making individual requests:
# Instead of creating contacts one at a time, use bulk tag update
POST /contacts/bulk/tags/update/add
{
"locationId": "LOC_ID",
"contactIds": ["id1", "id2", "id3"],
"tags": ["imported"]
}
Cache frequently accessed data
Cache reference data that doesn’t change often:
- Pipeline stages
- Calendar configurations
- Custom field definitions
- User/team member lists
Use efficient queries
- Always specify
limit to control response size
- Use search endpoints with filters instead of fetching all records and filtering locally
- Request only the fields you need (where supported)
Monitoring
- Log all API calls including response status, latency, and rate limit headers
- Track webhook deliveries and monitor for failures
- Set up alerts for elevated error rates or authentication failures
- Monitor token refresh cycles to catch issues before they affect users
Next steps
Last modified on March 5, 2026