This comprehensive guide will teach you how to build any type of extension for the Tradly platform. We use practical examples throughout, but the patterns apply to all extension types (shipping, payment, notification, order management, etc.).

1️⃣ Extension Overview

What is a Tradly Extension?

A Tradly extension is a microservice that extends platform functionality by:

  • Listening to events from Tradly via webhooks
  • Processing business logic for your specific use case
  • Integrating with external services (shipping providers, payment gateways, etc.)
  • Updating Tradly with results through API calls

Common Extension Types:

  • Shipping: Handle deliveries, tracking, label printing
  • Payment: Process payments, refunds, wallets
  • Notification: Send emails, SMS, push notifications
  • Translation: Multi-language content management
  • Order Management: Custom workflows, inventory sync

2️⃣ Project Setup

Basic Project Structure:

your-extension/
├── src/
│   ├── controllers/
│   │   └── feature/
│   │       ├── controller.ts
│   │       ├── services.ts
│   │       └── actions/
│   ├── shared/
│   │   ├── catchAsync.ts
│   │   └── sendResponse.ts
│   ├── config/
│   │   └── index.ts
│   └── app.ts
├── configs.json
├── package.json
├── .env
└── README.md

Initialize Project:

mkdir your-extension
cd your-extension
npm init -y
npm install express axios dotenv http-status cors
npm install -D @types/express @types/node typescript ts-node

Basic Dependencies:

{
  "dependencies": {
    "express": "^4.18.0",
    "axios": "^1.6.0",
    "dotenv": "^16.0.0",
    "http-status": "^1.7.0",
    "cors": "^2.8.5"
  },
  "devDependencies": {
    "@types/express": "^4.17.0",
    "@types/node": "^20.0.0",
    "typescript": "^5.0.0"
  }
}

3️⃣ Configuration Files

configs.json - Define Your Webhooks

This file tells Tradly which events to send to your extension:

{
    "name": "your-extension-name",
    "api_events": [
        {
            "route": "/your-feature/create",
            "events": ["order.confirmed"]
        },
        {
            "route": "/your-feature/update",
            "events": ["order.updated"]
        },
        {
            "route": "/your-feature/cancel",
            "events": ["order.canceled"]
        }
    ]
}

Key Components:

  • name: Unique identifier for your extension
  • route: Your endpoint path (must match your Express routes)
  • events: Tradly events that trigger this route

Common Tradly Events:

  • Orders: order.created, order.confirmed, order.updated, order.canceled
  • Payments: payment.created, payment.confirmed, payment.failed
  • Listings: listing.created, listing.updated, listing.deleted
  • Users: user.created, user.updated

4️⃣ Environment Setup

Configuration File:

// config/index.ts
import dotenv from 'dotenv';
dotenv.config();

const config = {
    port: process.env.PORT || 3000,
    
    // Tradly Settings
    tradly: {
        api_endpoint: process.env.TRADLY_API_ENDPOINT || 'https://api.tradly.app',
        secret_key: process.env.TRADLY_SK_KEY, // Get from SuperAdmin
    },
    
    // Your External Service Settings
    external_service: {
        api_endpoint: process.env.EXTERNAL_API_ENDPOINT,
        api_key: process.env.EXTERNAL_API_KEY,
        username: process.env.EXTERNAL_USERNAME,
        password: process.env.EXTERNAL_PASSWORD
    },
    
    integration_name: "your-extension"
};

export default config;

Environment Variables (.env):

# Server
PORT=3000

# Tradly (Get SK key from SuperAdmin)
TRADLY_API_ENDPOINT=https://api.tradly.app
TRADLY_SK_KEY=your_secret_key_here

# External Service
EXTERNAL_API_ENDPOINT=https://api.your-service.com
EXTERNAL_API_KEY=your_api_key
EXTERNAL_USERNAME=your_username
EXTERNAL_PASSWORD=your_password

How to Get Tradly SK Key:

  1. Login to Tradly SuperAdmin
  2. Go to your settings
  3. Click “API” menu
  4. Copy the SK key
  5. Add to your .env file

5️⃣ Controller Implementation

Shared Utilities:

// shared/catchAsync.ts
import { Request, Response, NextFunction } from 'express';

const catchAsync = (fn: Function) => {
    return (req: Request, res: Response, next: NextFunction) => {
        Promise.resolve(fn(req, res, next)).catch(next);
    };
};

export default catchAsync;
// shared/sendResponse.ts
import { Response } from 'express';

const sendResponse = (res: Response, data: any) => {
    res.status(data.status_code).json({
        success: data.success,
        message: data.message,
        data: data.data || null
    });
};

export default sendResponse;

Controller Template:

// controllers/feature/controller.ts
import { Request, Response } from "express";
import { FeatureServices } from "./services";
import catchAsync from "../../shared/catchAsync";
import sendResponse from "../../shared/sendResponse";
import httpStatus from "http-status";

// Handle create events from Tradly
const handleCreate = catchAsync(async (req: Request, res: Response) => {
    const { ...eventData } = req.body;
    console.log('Create event received:', eventData);
    
    const result = await FeatureServices.processCreate(eventData);

    sendResponse(res, {
        status_code: httpStatus.OK,
        data: result,
        message: "Create event processed successfully",
        success: true,
    });
});

// Handle update events from Tradly
const handleUpdate = catchAsync(async (req: Request, res: Response) => {
    const { ...eventData } = req.body;
    console.log('Update event received:', eventData);
    
    const result = await FeatureServices.processUpdate(eventData);

    sendResponse(res, {
        status_code: httpStatus.OK,
        data: result,
        message: "Update event processed successfully",
        success: true,
    });
});

// Handle cancellation events
const handleCancel = catchAsync(async (req: Request, res: Response) => {
    const { ...eventData } = req.body;
    console.log('Cancel event received:', eventData);
    
    const result = await FeatureServices.processCancel(eventData);

    sendResponse(res, {
        status_code: httpStatus.OK,
        data: result,
        message: "Cancel event processed successfully",
        success: true,
    });
});

export const FeatureController = {
    handleCreate,
    handleUpdate,
    handleCancel
};

6️⃣ Service Layer

Services Template:

// controllers/feature/services.ts
import { callExternalAPI } from "./actions/external_api";
import { saveToTradly } from "./actions/save_to_tradly";
import { updateTradlyStatus } from "./actions/update_status";
import { getExternalAuth } from "./actions/auth";

// Process create events
const processCreate = async (eventData: any): Promise<any | null> => {
    try {
        console.log('Processing create event...');

        // Step 1: Get authentication for external service
        const authData = await getExternalAuth();
        
        if (!authData || !eventData?.data) {
            console.log('Missing auth or event data');
            return null;
        }

        // Step 2: Call external API
        const externalResponse = await callExternalAPI({
            data: eventData.data,
            auth: authData,
            action: 'create'
        });

        // Step 3: Save response back to Tradly
        if (externalResponse) {
            await saveToTradly({
                tradlyData: eventData.data,
                externalResponse: externalResponse,
            });
        }

        return { success: true, external_id: externalResponse?.id };
    } catch (error) {
        console.error('Error in processCreate:', error);
        return null;
    }
};

// Process update events
const processUpdate = async (eventData: any): Promise<any | null> => {
    try {
        console.log('Processing update event...');
        
        const authData = await getExternalAuth();
        
        if (authData && eventData?.data) {
            await callExternalAPI({
                data: eventData.data,
                auth: authData,
                action: 'update'
            });
        }

        return { success: true };
    } catch (error) {
        console.error('Error in processUpdate:', error);
        return null;
    }
};

// Process cancellation events
const processCancel = async (eventData: any): Promise<any | null> => {
    try {
        console.log('Processing cancel event...');
        
        const authData = await getExternalAuth();
        
        if (authData && eventData?.data?.external_id) {
            await callExternalAPI({
                data: { id: eventData.data.external_id },
                auth: authData,
                action: 'cancel'
            });
        }

        return { success: true };
    } catch (error) {
        console.error('Error in processCancel:', error);
        return null;
    }
};

// Get external data (rates, locations, etc.)
const getExternalData = async (requestData: any): Promise<any[]> => {
    try {
        const authData = await getExternalAuth();
        
        if (!authData) {
            return [];
        }

        const externalData = await callExternalAPI({
            data: requestData,
            auth: authData,
            action: 'fetch'
        });

        // Transform data to Tradly format
        return transformData(externalData);
    } catch (error) {
        console.error('Error in getExternalData:', error);
        return [];
    }
};

// Helper function to transform external data
const transformData = (externalData: any): any[] => {
    if (!externalData || !Array.isArray(externalData)) {
        return [];
    }

    return externalData.map(item => ({
        id: item.id,
        name: item.name || item.title,
        price: item.cost || item.price || null,
        // Add other fields as needed for your use case
    }));
};

export const FeatureServices = {
    processCreate,
    processUpdate,
    processCancel,
    getExternalData,
};

7️⃣ Tradly API Integration

Save Data to Tradly:

// controllers/feature/actions/save_to_tradly.ts
import axios from "axios";
import config from "../../../config";

export const saveToTradly = async ({
    tradlyData,
    externalResponse,
}: {
    tradlyData: any;
    externalResponse: any;
}) => {
    try {
        const orderId = tradlyData?.id;
        const shipmentId = tradlyData?.shipments?.[0]?.id;
        
        if (!orderId) {
            console.log('Missing order ID');
            return null;
        }

        // Example: Update order with external data
        const url = shipmentId 
            ? `${config.tradly.api_endpoint}/products/v1/orders/${orderId}/shipments/${shipmentId}`
            : `${config.tradly.api_endpoint}/products/v1/orders/${orderId}`;

        const data = JSON.stringify({
            operation: "update_tracking", // or other operation
            shipment: {
                external_shipment_id: externalResponse?.id,
                external_shipment_type: config.integration_name,
                tracking_number: externalResponse?.tracking_number,
            },
        });

        const response = await axios({
            method: "patch",
            url: url,
            headers: {
                "Content-Type": "application/json",
                Authorization: `Bearer ${config.tradly.secret_key}`,
            },
            data: data,
        });

        console.log('Data saved to Tradly:', response.data);
        return response.data;
    } catch (error) {
        console.error('Error saving to Tradly:', error);
        return null;
    }
};

Update Status in Tradly:

// controllers/feature/actions/update_status.ts
import axios from "axios";
import config from "../../../config";

export const updateTradlyStatus = async ({
    orderId,
    status,
    callbackData
}: {
    orderId: string;
    status: string;
    callbackData?: any;
}) => {
    try {
        // Map external status to Tradly status
        const tradlyStatus = mapStatusToTradly(status);
        
        if (!tradlyStatus) {
            console.log('Status not mapped:', status);
            return null;
        }

        const url = `${config.tradly.api_endpoint}/products/v1/orders/${orderId}`;

        const data = JSON.stringify({
            operation: "update_status",
            status: tradlyStatus,
            status_message: `Status updated to ${status}`,
        });

        const response = await axios({
            method: "patch",
            url: url,
            headers: {
                "Content-Type": "application/json",
                Authorization: `Bearer ${config.tradly.secret_key}`,
            },
            data: data,
        });

        console.log('Status updated in Tradly:', response.data);
        return response.data;
    } catch (error) {
        console.error('Error updating status:', error);
        return null;
    }
};

// Map external service status to Tradly status
const mapStatusToTradly = (externalStatus: string): string | null => {
    const statusMap: { [key: string]: string } = {
        'created': 'pending',
        'confirmed': 'confirmed',
        'in_transit': 'shipped',
        'delivered': 'delivered',
        'cancelled': 'cancelled',
        // Add mappings for your external service
    };

    return statusMap[externalStatus.toLowerCase()] || null;
};

8️⃣ External API Integration

Authentication:

// controllers/feature/actions/auth.ts
import axios from "axios";
import config from "../../../config";

let cachedAuth: any = null;
let authExpiry: Date | null = null;

export const getExternalAuth = async (): Promise<any | null> => {
    try {
        // Return cached auth if still valid
        if (cachedAuth && authExpiry && new Date() < authExpiry) {
            return cachedAuth;
        }

        // Get new authentication
        const response = await axios({
            method: 'post',
            url: `${config.external_service.api_endpoint}/auth/login`, // Adjust URL
            data: {
                username: config.external_service.username,
                password: config.external_service.password,
                // Or use API key, client credentials, etc.
            },
            headers: {
                'Content-Type': 'application/json',
            },
        });

        cachedAuth = response.data;
        
        // Set expiry (default 1 hour if not provided)
        const expiresIn = response.data.expires_in || 3600;
        authExpiry = new Date(Date.now() + (expiresIn * 1000));

        console.log('External authentication successful');
        return cachedAuth;
    } catch (error) {
        console.error('External authentication failed:', error);
        return null;
    }
};

API Calls:

// controllers/feature/actions/external_api.ts
import axios from "axios";
import config from "../../../config";

export const callExternalAPI = async ({
    data,
    auth,
    action
}: {
    data: any;
    auth: any;
    action: string;
}) => {
    try {
        let url: string;
        let method: string;
        let requestData: any;

        // Configure based on action
        switch (action) {
            case 'create':
                url = `${config.external_service.api_endpoint}/orders`;
                method = 'post';
                requestData = transformToExternalFormat(data);
                break;
            case 'update':
                url = `${config.external_service.api_endpoint}/orders/${data.id}`;
                method = 'put';
                requestData = transformToExternalFormat(data);
                break;
            case 'cancel':
                url = `${config.external_service.api_endpoint}/orders/${data.id}/cancel`;
                method = 'post';
                requestData = { reason: 'Customer cancellation' };
                break;
            case 'fetch':
                url = `${config.external_service.api_endpoint}/rates`;
                method = 'post';
                requestData = data;
                break;
            default:
                throw new Error('Invalid action');
        }

        const response = await axios({
            method,
            url,
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${auth.access_token}`, // Adjust auth header
            },
            data: requestData,
        });

        console.log(`External API ${action} successful:`, response.data);
        return response.data;
    } catch (error) {
        console.error(`External API ${action} failed:`, error);
        return null;
    }
};

// Transform Tradly data to external service format
const transformToExternalFormat = (tradlyData: any): any => {
    return {
        // Transform Tradly order data to your external service format
        reference: tradlyData.id,
        // Add other fields as needed
    };
};

9️⃣ Deployment & Registration

App Setup:

// app.ts
import express from 'express';
import cors from 'cors';
import { FeatureController } from './controllers/feature/controller';

const app = express();

app.use(cors());
app.use(express.json());

// Health check
app.get('/health', (req, res) => {
    res.json({ status: 'OK', timestamp: new Date().toISOString() });
});

// Feature routes (must match configs.json)
app.post('/your-feature/create', FeatureController.handleCreate);
app.post('/your-feature/update', FeatureController.handleUpdate);
app.post('/your-feature/cancel', FeatureController.handleCancel);
 
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`Extension running on port ${PORT}`);
});

Deployment Steps:

  1. Deploy to Cloud Provider:
# Example for Vercel
npm install -g vercel
vercel

# Example for Heroku
git add .
git commit -m "Deploy extension"
git push heroku main
  1. Register in SuperAdmin:

    • Go to SuperAdmin Extensions
    • Upload your configs.json file
    • Enter your deployed URL (without https://)
    • Save and activate
  2. Test Your Extension:

    • Create a test order in Tradly
    • Check your logs for webhook events
    • Verify data is saved correctly

🔟 Testing & Troubleshooting

Common Issues:

Webhook not triggering?

  • Check configs.json is uploaded and active
  • Verify your deployed URL is accessible
  • Ensure route paths match exactly

Authentication issues?

  • Verify SK key is correct
  • Check external service credentials
  • Monitor auth token expiry

Data not saving?

  • Check Tradly API response
  • Verify order/shipment IDs exist
  • Review data transformation logic

Testing Tips:

Local Testing:

# Use ngrok for local webhook testing
npm install -g ngrok
ngrok http 3000

Logging:

console.log('Event received:', JSON.stringify(req.body, null, 2));

Error Handling:

try {
    // Your code
} catch (error) {
    console.error('Error:', error);
    // Don't throw - return graceful response
    return { success: false, error: error.message };
}

Best Practices

  • Always use environment variables for sensitive data
  • Handle errors gracefully - don’t crash on invalid data
  • Log everything - you’ll need it for debugging
  • Cache authentication - don’t auth on every request
  • Validate webhook data - check required fields exist
  • Use async/await - easier to read than promises
  • Keep routes simple - business logic goes in services
  • Test with real data - use Tradly staging environment

1️⃣1️⃣ API Testing & Documentation

🔧 Essential API Testing Setup

Before building your extension, you need to understand what Tradly APIs are available and how to use them properly. This is a critical step that will save you hours of debugging.

📚 Download Tradly API Documentation

Step 1: Get the Postman Collection

  1. Visit the official Tradly API documentation: https://developer.tradly.app/
  2. Download the Postman collection for complete API reference
  3. Import the collection into Postman for easy testing

Step 2: Study Available APIs Before writing any code, carefully review:

  • Available endpoints for your use case
  • Required request parameters
  • Response formats
  • Error handling patterns
  • Authentication requirements

🔑 Authentication Setup (CRITICAL)

⚠️ Important: SK Key in Headers

Brother Thahaseen, when making API calls to Tradly, you MUST pass the SK (Secret Key) in the Authorization header:

headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${your_sk_key_here}`
}

How to get your SK Key:

  1. Login to Tradly SuperAdmin
  2. Navigate to Settings → API Settings
  3. Copy your Secret Key (SK)
  4. Store it securely in your environment variables

🧪 Pre-Development API Testing

Before writing extension code, test these APIs manually:

// Example: Test Order API
const testOrderAPI = async () => {
    const response = await axios({
        method: 'GET',
        url: 'https://api.tradly.app/products/v1/orders',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${process.env.TRADLY_SK_KEY}`
        }
    });
    
    console.log('Orders:', response.data);
};

📝 API Testing Checklist

For each API you plan to use:

Test the endpoint manually in Postman

  • Verify the URL is correct
  • Check required headers (especially Authorization)
  • Test with sample data
  • Document the response structure

Understand the data flow

  • What data does Tradly send in webhooks?
  • What data do you need to send back?
  • How should you transform the data?

Test error scenarios

  • What happens with invalid data?
  • How does authentication failure look?
  • What are the rate limits?

Document your findings

  • Save working API calls
  • Note any quirks or special requirements
  • Create a reference for your team

🎯 Common API Endpoints to Test

Orders:

GET /products/v1/orders
POST /products/v1/orders
PATCH /products/v1/orders/{id}

Shipments:

GET /products/v1/orders/{order_id}/shipments
PATCH /products/v1/orders/{order_id}/shipments/{shipment_id}

Products:

GET /products/v1/listings
POST /products/v1/listings
PATCH /products/v1/listings/{id}

🚨 Critical Testing Steps

1. Authentication Test:

curl -X GET "https://api.tradly.app/products/v1/orders" \
  -H "Authorization: Bearer YOUR_SK_KEY" \
  -H "Content-Type: application/json"

2. Webhook Simulation: Create a simple test endpoint to see what data Tradly sends:

app.post('/test-webhook', (req, res) => {
    console.log('Webhook received:', JSON.stringify(req.body, null, 2));
    res.json({ success: true });
});

3. Data Transformation Test: Ensure you can transform data between formats:

const testTransformation = (tradlyData) => {
    // Test converting Tradly data to your external service format
    const transformed = transformToExternalFormat(tradlyData);
    console.log('Original:', tradlyData);
    console.log('Transformed:', transformed);
    return transformed;
};

📋 API Testing Best Practices

Before Development:

  • Test all APIs manually with Postman
  • Document response formats
  • Understand authentication requirements
  • Verify data structures match your needs

During Development:

  • Test each API integration as you build it
  • Log all requests and responses
  • Handle edge cases and errors
  • Validate data transformations

After Development:

  • Test with real webhook data
  • Verify end-to-end functionality
  • Monitor API rate limits
  • Document any custom implementations

🎯 Final Testing Reminder

Remember: The key to successful extension development is understanding the APIs before you start coding. Spend time with the Postman collection, test different scenarios, and make sure you understand:

  1. What data Tradly sends (via webhooks)
  2. What APIs you can call (to update Tradly)
  3. How authentication works (SK key in headers)
  4. What data transformations you need (between systems)

This upfront investment in API understanding will save you countless hours of debugging later!


Support

For questions or issues:

  • Check Tradly Developer Docs: https://developer.tradly.app/
  • Download the Postman collection for API testing
  • Contact Tradly support
  • Review this guide’s examples

Remember: This guide provides the foundation - customize it for your specific external service and business requirements!

Got questions?

No spam. You can unsubscribe at any time.

Ask