URL: /baas/api/integration/flows/common/best-practices --- title: 'Best Practices' description: 'Implementation best practices for registration, verification, and activation' --- # Best Practices Follow these best practices to ensure smooth integration with the FinHub API. ## Registration Best Practices ### ✅ DO | Practice | Description | |----------|-------------| | **Call categorization hierarchy first** | Always retrieve available categories before registration | | **Build parametrization correctly** | Match feature requirements exactly | | **Use strong passwords** | Min 12 chars, mixed case, numbers, symbols | | **Store IDs securely** | Save customer ID and user ID for future calls | | **Validate email format** | Check email format before submission | | **Handle duplicates** | Check for existing customers before creating new ones | ### ❌ DON'T | Anti-Pattern | Issue | |--------------|-------| | Hardcode category IDs | IDs may vary across environments | | Skip categorization validation | Leads to registration failures | | Use weak passwords | Will be rejected by validation | | Expose credentials in logs | Security risk | | Submit duplicate emails | Will cause conflicts | | Ignore error responses | Miss important validation feedback | --- ## Session Management Best Practices ### Token Handling ```javascript // Good: Store token securely and check expiry const tokenData = { token: response.data.token, expiresAt: Date.now() + (response.data.expiresIn * 1000), refreshToken: response.data.refreshToken }; function isTokenValid() { return Date.now() < tokenData.expiresAt - 60000; // 1 min buffer } async function getValidToken() { if (!isTokenValid()) { await refreshToken(); } return tokenData.token; } ``` ### Session Security - **Never store tokens in localStorage** for sensitive applications - **Use httpOnly cookies** when possible - **Implement token refresh** before expiry - **Clear tokens on logout** from all storage --- ## Verification Best Practices ### ✅ DO | Practice | Description | |----------|-------------| | **Submit high-quality images** | Clear, well-lit document photos | | **Check document expiry** | Ensure documents are not expired | | **Provide all required documents** | Check `requiredDocuments` array | | **Keep customer informed** | Show verification status updates | | **Monitor progress** | Poll status endpoint periodically | | **Handle rejection gracefully** | Allow document resubmission | ### ❌ DON'T | Anti-Pattern | Issue | |--------------|-------| | Submit blurry images | Will be rejected | | Use expired documents | Verification will fail | | Skip required document types | Incomplete verification | | Assume immediate approval | High-risk may take 1-3 days | | Retry failed verification endlessly | Wait for guidance | ### Document Quality Guidelines | Requirement | Standard | |-------------|----------| | **Resolution** | Min 300 DPI | | **Format** | JPEG, PNG, or PDF | | **Size** | Max 5 MB per document | | **Clarity** | All text must be readable | | **Completeness** | All corners visible | --- ## Consent Management Best Practices ### ✅ DO | Practice | Description | |----------|-------------| | **Accept all three consents** | Terms, Privacy, Data Processing | | **Record acceptance metadata** | IP, timestamp, user agent | | **Store consent versions** | Track which version was accepted | | **Handle consent expiry** | Re-prompt when consents expire | ### Consent Acceptance Pattern ```javascript async function acceptAllConsents(customerId) { const consents = ['terms', 'privacy', 'data-processing']; const results = []; for (const type of consents) { const result = await acceptConsent(customerId, type, { accepted: true, version: '1.0', acceptanceTimestamp: new Date().toISOString() }); results.push(result); } return results; } ``` --- ## Activation Best Practices ### Pre-Activation Checklist ```javascript async function canActivate(customerId, tenantId) { const checks = { verification: await checkVerificationStatus(customerId), termsConsent: await checkConsent(customerId, 'TERMS_AND_CONDITIONS'), privacyConsent: await checkConsent(customerId, 'PRIVACY_POLICY'), dataConsent: await checkConsent(customerId, 'DATA_PROCESSING') }; const allPassed = checks.verification === 'APPROVED' && checks.termsConsent === 'ACCEPTED' && checks.privacyConsent === 'ACCEPTED' && checks.dataConsent === 'ACCEPTED'; return { canActivate: allPassed, checks: checks, missing: Object.entries(checks) .filter(([_, v]) => v !== 'APPROVED' && v !== 'ACCEPTED') .map(([k, _]) => k) }; } ``` ### ✅ DO | Practice | Description | |----------|-------------| | **Verify all prerequisites** | Check before attempting activation | | **Handle activation errors** | Parse error response for missing items | | **Inform customer of success** | Send confirmation notification | | **Store wallet details** | Save IBAN and wallet ID | ### ❌ DON'T | Anti-Pattern | Issue | |--------------|-------| | Attempt activation without verification | Will fail with 422 | | Skip consent acceptance | Will fail with 400 | | Retry activation repeatedly | Don't retry without fixing issues | | Expose sensitive data in errors | Log internally only | --- ## Transaction Best Practices ### Order Lifecycle ```mermaid flowchart LR A[Prepare] --> B{Valid?} B -->|Yes| C[Execute] B -->|No| D[Fix Issues] C --> E[Monitor] E --> F{Complete?} F -->|Yes| G[Done] F -->|No| E ``` ### ✅ DO | Practice | Description | |----------|-------------| | **Pre-register beneficiaries** | Add beneficiaries before transfers | | **Use prepare/execute pattern** | Always prepare before executing | | **Check limits before transfer** | Verify against daily/monthly limits | | **Implement idempotency** | Use unique references per transaction | | **Monitor order status** | Track until COMPLETED or FAILED | ### ❌ DON'T | Anti-Pattern | Issue | |--------------|-------| | Execute without prepare | Missing fee calculation | | Ignore order expiry | Prepared orders expire after 2-3 hours | | Retry failed orders blindly | May cause duplicate transactions | | Skip beneficiary validation | PEP/sanctions check required | --- ## API Integration Patterns ### Retry Strategy ```javascript async function apiCallWithRetry(fn, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { if (error.status === 429) { // Rate limited - exponential backoff await sleep(Math.pow(2, i) * 1000); continue; } if (error.status >= 500) { // Server error - retry with backoff await sleep(Math.pow(2, i) * 1000); continue; } // Client error (4xx) - don't retry throw error; } } throw new Error('Max retries exceeded'); } ``` ### Idempotency ```javascript // Generate unique reference for each transaction function generateReference(prefix, customerId) { const timestamp = Date.now(); const random = Math.random().toString(36).substring(2, 8); return `${prefix}-${customerId.slice(-8)}-${timestamp}-${random}`; } // Example: TXN-550e8400-1705315200000-a1b2c3 ``` --- ## Security Best Practices | Area | Recommendation | |------|----------------| | **API Keys** | Store in environment variables, never in code | | **Tokens** | Use short-lived access tokens with refresh | | **Passwords** | Never log or store in plain text | | **PII Data** | Encrypt at rest and in transit | | **Error Messages** | Don't expose internal details to users | | **Audit Logging** | Log all API calls with sanitized data | --- ## Performance Best Practices | Area | Recommendation | |------|----------------| | **Batch Operations** | Use bulk endpoints where available | | **Caching** | Cache categorization hierarchy | | **Pagination** | Use pagination for list endpoints | | **Async Processing** | Don't block on long operations | | **Connection Pooling** | Reuse HTTP connections |