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:
- Login to Tradly SuperAdmin
- Go to your settings
- Click “API” menu
- Copy the SK key
- 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:
- 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
Register in SuperAdmin:
- Go to SuperAdmin Extensions
- Upload your configs.json file
- Enter your deployed URL (without https://)
- Save and activate
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
- Visit the official Tradly API documentation: https://developer.tradly.app/
- Download the Postman collection for complete API reference
- 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:
- Login to Tradly SuperAdmin
- Navigate to Settings → API Settings
- Copy your Secret Key (SK)
- 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:
- What data Tradly sends (via webhooks)
- What APIs you can call (to update Tradly)
- How authentication works (SK key in headers)
- 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!