Authentication
Store Credentials Securely
Never hard-code API credentials in your source code!
// Use environment variables
const appSecret = process . env . DUKKANGO_APP_SECRET ;
const restSecret = process . env . DUKKANGO_REST_SECRET ;
// Or secure key management
const credentials = await keyVault . getSecret ( 'dukkango-api-keys' );
// Never do this!
const appSecret = 'my-secret-key-123' ;
const restSecret = 'restaurant-key-456' ;
Implement Token Refresh
Proactive Refresh Strategy
Don’t wait for tokens to expire. Refresh them before expiration. class TokenManager {
constructor () {
this . token = null ;
this . expiresAt = null ;
}
async getToken () {
// Refresh if expired or expiring within 1 hour
if ( ! this . token || Date . now () > this . expiresAt - 3600000 ) {
await this . refresh ();
}
return this . token ;
}
async refresh () {
const response = await login ();
this . token = response . access_token ;
this . expiresAt = new Date ( response . expiration_date ). getTime ();
}
}
Always catch authentication errors and retry after refreshing token. async function apiCall ( endpoint , options ) {
try {
return await fetch ( endpoint , {
... options ,
headers: {
... options . headers ,
'Access-Token' : await tokenManager . getToken ()
}
});
} catch ( error ) {
if ( error . status === 401 ) {
// Force refresh and retry once
await tokenManager . refresh ();
return await fetch ( endpoint , {
... options ,
headers: {
... options . headers ,
'Access-Token' : await tokenManager . getToken ()
}
});
}
throw error ;
}
}
Order Management
Always Acknowledge Orders
Import to POS
Store order in your local database/system
Acknowledge Immediately
PUT /orders/success/{payment_key}
Don’t wait! Acknowledge right after importing.
If you don’t acknowledge orders, they will repeat in every /get-current call, causing duplicate imports!
Use Correct IDs
// Use payment_key (UUID) for API operations
const order = await getOrders ();
await acceptOrder ( order . payment_key ); // ✅
await successOrder ( order . payment_key ); // ✅
await completeOrder ( order . payment_key ); // ✅
// Don't use id (integer)
const order = await getOrders ();
await acceptOrder ( order . id ); // ❌ Will fail!
Display: Use id (integer) for showing to users
API Calls: Use payment_key (UUID) for all operations
Handle All Statuses
Your POS should gracefully handle orders in any status:
function handleOrder ( order ) {
switch ( order . status_id ) {
case 1 : // RECEIVED
return displayNewOrder ( order );
case 2 : // CONFIRMED
return displayPreparingOrder ( order );
case 16 : // IN_DELIVERY
return displayDeliveryOrder ( order );
case 5 : // COMPLETE
return archiveOrder ( order );
case 6 : // CANCELED
return markCanceled ( order );
default :
console . warn ( `Unknown status: ${ order . status_id } ` );
return displayOrder ( order ); // Fallback
}
}
Polling Strategy
Respect Rate Limits
/orders/get-current and /foods/get-foods have 30-second cooldowns.
// ✅ Good: Poll every 30+ seconds
setInterval ( pollOrders , 30000 );
// ❌ Bad: Too frequent
setInterval ( pollOrders , 5000 ); // Will hit rate limit!
Implement Exponential Backoff
async function pollWithBackoff () {
let delay = 30000 ; // Start with 30s
const maxDelay = 300000 ; // Max 5 minutes
while ( true ) {
try {
await pollOrders ();
delay = 30000 ; // Reset on success
} catch ( error ) {
if ( error . status === 429 ) {
// Rate limited - increase delay
delay = Math . min ( delay * 2 , maxDelay );
console . log ( `Rate limited. Waiting ${ delay } ms` );
}
}
await sleep ( delay );
}
}
Don’t fetch the full menu on every order! Cache it locally.
class MenuCache {
constructor () {
this . menu = null ;
this . lastFetch = null ;
this . cacheDuration = 30 * 60 * 1000 ; // 30 minutes
}
async getMenu () {
const now = Date . now ();
// Return cached if recent
if ( this . menu && ( now - this . lastFetch ) < this . cacheDuration ) {
return this . menu ;
}
// Fetch fresh menu
this . menu = await fetchMenu ();
this . lastFetch = now ;
return this . menu ;
}
invalidate () {
this . menu = null ;
this . lastFetch = null ;
}
}
Error Handling
Implement Comprehensive Error Handling
async function makeApiCall ( endpoint , options ) {
try {
const response = await fetch ( endpoint , options );
const data = await response . json ();
if ( ! data . status ) {
// API returned error
throw new ApiError ( data . error , data . message , response . status );
}
return data . data ;
} catch ( error ) {
// Handle specific errors
if ( error instanceof ApiError ) {
handleApiError ( error );
} else if ( error . name === 'TypeError' ) {
// Network error
console . error ( 'Network error:' , error );
throw new Error ( 'Unable to connect to API' );
} else {
// Unknown error
console . error ( 'Unexpected error:' , error );
throw error ;
}
}
}
function handleApiError ( error ) {
switch ( error . status ) {
case 401 :
// Token expired - refresh and retry
return refreshTokenAndRetry ();
case 429 :
// Rate limited - wait and retry
return waitAndRetry ( 30000 );
case 404 :
// Not found - log and notify
logError ( 'Resource not found' , error );
notifyUser ( 'Order not found' );
break ;
case 422 :
// Validation error - fix and retry
logError ( 'Validation failed' , error );
notifyUser ( 'Invalid request' );
break ;
default :
// Other errors
logError ( 'API error' , error );
notifyUser ( 'Something went wrong' );
}
}
Log Errors with Context
function logError ( message , error , context = {}) {
console . error ({
message ,
error: error . message ,
stack: error . stack ,
timestamp: new Date (). toISOString (),
context: {
... context ,
userId: currentUser ?. id ,
restaurantId: currentRestaurant ?. id
}
});
// Send to error tracking service
errorTracker . captureException ( error , context );
}
Data Validation
Validate Before Sending
Accept Order
Cancel Order
function validateAcceptRequest ( preparingTime ) {
if ( typeof preparingTime !== 'number' ) {
throw new Error ( 'preparing_time must be a number' );
}
if ( preparingTime < 5 || preparingTime > 120 ) {
throw new Error ( 'preparing_time must be between 5 and 120 minutes' );
}
return true ;
}
// Use before API call
validateAcceptRequest ( preparingTime );
await acceptOrder ( orderId , preparingTime );
function validateCancelRequest ( reasonId ) {
const validReasons = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ];
if ( ! validReasons . includes ( reasonId )) {
throw new Error ( `Invalid reason_id: ${ reasonId } ` );
}
return true ;
}
// Use before API call
validateCancelRequest ( reasonId );
await cancelOrder ( orderId , reasonId );
Validate Received Data
function validateOrder ( order ) {
const required = [ 'id' , 'payment_key' , 'status_id' , 'customer' , 'foods' ];
for ( const field of required ) {
if ( ! ( field in order )) {
throw new Error ( `Missing required field: ${ field } ` );
}
}
// Validate UUIDs
const uuidRegex = / ^ [ 0-9a-f ] {8} - [ 0-9a-f ] {4} - [ 0-9a-f ] {4} - [ 0-9a-f ] {4} - [ 0-9a-f ] {12} $ / i ;
if ( ! uuidRegex . test ( order . payment_key )) {
throw new Error ( 'Invalid payment_key format' );
}
return true ;
}
Use Connection Pooling
// Reuse HTTP connections
const agent = new https . Agent ({
keepAlive: true ,
maxSockets: 10
});
const apiClient = axios . create ({
httpsAgent: agent ,
timeout: 30000
});
Implement Request Timeouts
async function apiCallWithTimeout ( endpoint , options , timeout = 30000 ) {
const controller = new AbortController ();
const timeoutId = setTimeout (() => controller . abort (), timeout );
try {
const response = await fetch ( endpoint , {
... options ,
signal: controller . signal
});
return response ;
} finally {
clearTimeout ( timeoutId );
}
}
Batch Operations When Possible
// ❌ Bad: Update items one by one
for ( const item of menuItems ) {
await updateItemStatus ( item . id , 'passive' );
}
// ✅ Good: Collect and process in batches
const updates = menuItems . map ( item => ({
id: item . id ,
status: 'passive'
}));
// Process updates efficiently
await batchUpdateItems ( updates );
Testing
Test All Scenarios
Place test order
Accept order
Mark on the way
Complete order
Verify status at each step
Invalid credentials
Expired token
Rate limiting
Network timeouts
Invalid order IDs
Missing required fields
Orders with no modifiers
Orders with multiple modifier groups
Scheduled orders
Cash vs card payments
Different courier types
Order repetition after status change
Test Environment
const config = {
development: {
baseUrl: 'https://posapitest.fuudy.co' ,
logLevel: 'debug'
},
production: {
baseUrl: 'https://www.xn--dkkango-n2a.com/api/integrations' ,
logLevel: 'error'
}
};
const env = process . env . NODE_ENV || 'development' ;
const apiConfig = config [ env ];
Monitoring
Track Key Metrics
class ApiMetrics {
constructor () {
this . metrics = {
ordersReceived: 0 ,
ordersAccepted: 0 ,
ordersCanceled: 0 ,
ordersCompleted: 0 ,
apiErrors: 0 ,
responseTime: []
};
}
recordOrder ( status ) {
this . metrics [ status ] ++ ;
}
recordResponseTime ( duration ) {
this . metrics . responseTime . push ( duration );
}
recordError () {
this . metrics . apiErrors ++ ;
}
getStats () {
return {
... this . metrics ,
avgResponseTime: average ( this . metrics . responseTime ),
errorRate: this . metrics . apiErrors / this . getTotalRequests ()
};
}
}
Set Up Alerts
Monitor for:
High error rates (> 5%)
Slow response times (> 5 seconds)
Missing orders (gaps in order IDs)
Authentication failures
Rate limit hits
Security Checklist
Complete Security Checklist
Summary
Do's
Store credentials securely
Acknowledge orders immediately
Use payment_key for API calls
Respect rate limits (30s)
Cache menu data locally
Implement error handling
Validate all data
Log errors with context
Monitor API health
Test thoroughly
Don'ts
Hard-code credentials
Forget to acknowledge orders
Use integer ID for operations
Poll more than every 30s
Fetch menu on every request
Ignore error responses
Skip data validation
Log sensitive data
Deploy without monitoring
Skip testing
Need Help? Have questions about best practices? Contact our integration support team.