ICourierPlugin Interface
The contract every courier plugin must implement to integrate with Deliverty Hub.
Interface Definition
Every courier plugin must implement the ICourierPlugin interface. This is the core contract between your plugin and Deliverty Hub. The system calls these methods at the appropriate points in the delivery lifecycle.
interface ICourierPlugin {
readonly name: string; // e.g., 'bosta', 'dhl'
readonly version: string; // plugin version
initialize(config: Record<string, any>): Promise<void>;
createTask(taskData: ITaskData): Promise<IIntegrationResult>;
cancelTask(externalTrackingId: string): Promise<IIntegrationResult>;
getTracking(externalTrackingId: string): Promise<ITrackingInfo>;
validateWebhook(payload: any, signature?: string): Promise<boolean>;
parseWebhook(payload: any): Promise<IWebhookPayload>;
supportsFeature(feature: string): boolean;
healthCheck(): Promise<{ healthy: boolean; message?: string; details?: any }>;
}
Properties
name (readonly string)
A unique identifier for your courier plugin, typically your company or service name in lowercase. This is used internally by Deliverty Hub to route tasks to the correct plugin. Examples: 'bosta', 'dhl', 'aramex'.
version (readonly string)
The version of your plugin, following semantic versioning (e.g., '1.0.0', '2.3.1'). This helps the Deliverty team track which version of your plugin is deployed and manage upgrades.
Methods
initialize(config)
| Parameter | Type | Description |
|---|---|---|
config |
Record<string, any> |
Organization-specific configuration including API credentials, base URLs, and any custom settings. |
Called once when the plugin is loaded for a specific organization. Use this method to set up HTTP clients, validate credentials, establish connections, or perform any other one-time setup. The config object contains the organization's credentials and settings for your service.
async initialize(config: Record<string, any>): Promise<void> {
this.apiKey = config.apiKey;
this.baseUrl = config.baseUrl || 'https://api.mycourier.com/v1';
this.httpClient = axios.create({
baseURL: this.baseUrl,
headers: { 'Authorization': `Bearer ${this.apiKey}` },
});
}
createTask(taskData)
| Parameter | Type | Description |
|---|---|---|
taskData |
ITaskData |
The delivery task details including pickup/dropoff locations, package info, and customer details. |
Returns: Promise<IIntegrationResult> containing a success/failure status and an external tracking ID.
Called when Deliverty Hub dispatches a delivery task to your courier service. Your implementation should call your own API to create the delivery and return the external tracking ID that Deliverty Hub will use for all future operations on this task.
cancelTask(externalTrackingId)
| Parameter | Type | Description |
|---|---|---|
externalTrackingId |
string |
The tracking ID returned by your createTask() method. |
Returns: Promise<IIntegrationResult> indicating whether the cancellation succeeded.
Called when an organization cancels a task that was previously dispatched to your service. Your implementation should cancel the delivery on your end.
getTracking(externalTrackingId)
| Parameter | Type | Description |
|---|---|---|
externalTrackingId |
string |
The tracking ID returned by your createTask() method. |
Returns: Promise<ITrackingInfo> with the current delivery status, location, and timestamps.
Called when Deliverty Hub needs to poll for the current status of a delivery. Return the most up-to-date tracking information including the delivery status, driver location (if available), and estimated delivery time.
validateWebhook(payload, signature?)
| Parameter | Type | Description |
|---|---|---|
payload |
any |
The raw webhook payload received from your courier service. |
signature |
string (optional) |
The webhook signature header value, if your service signs webhooks. |
Returns: Promise<boolean> — true if the webhook is valid, false otherwise.
Called when Deliverty Hub receives an incoming webhook from your courier service. Use this method to verify the webhook's authenticity, typically by validating an HMAC signature or checking a shared secret. This prevents malicious actors from sending fake status updates.
parseWebhook(payload)
| Parameter | Type | Description |
|---|---|---|
payload |
any |
The raw webhook payload from your courier service (already validated). |
Returns: Promise<IWebhookPayload> containing a standardized event type, tracking ID, and status.
Called after validateWebhook() returns true. Converts your courier-specific webhook format into Deliverty Hub's standardized IWebhookPayload format. This is where you map your statuses (e.g., "picked_up") to Deliverty Hub's status system.
supportsFeature(feature)
| Parameter | Type | Description |
|---|---|---|
feature |
string |
The feature name to check (e.g., 'realtime-tracking', 'cancellation'). |
Returns: boolean — true if your plugin supports the given feature.
A synchronous method that lets Deliverty Hub query your plugin's capabilities at runtime. Common feature strings include:
'realtime-tracking'— whether your service provides live GPS tracking'cancellation'— whether deliveries can be cancelled after dispatch'proof-of-delivery'— whether your service captures delivery confirmation'scheduling'— whether deliveries can be scheduled for a future time
healthCheck()
Returns: Promise<{ healthy: boolean; message?: string; details?: any }>
Called periodically by Deliverty Hub to verify that your plugin and its external dependencies are operational. Use this to check connectivity to your API, validate that credentials are still valid, or verify any other critical dependencies.
async healthCheck(): Promise<{ healthy: boolean; message?: string; details?: any }> {
try {
const response = await this.httpClient.get('/health');
return { healthy: true, message: 'API reachable', details: { latency: response.duration } };
} catch (error) {
return { healthy: false, message: `API unreachable: ${error.message}` };
}
}
Example Plugin Implementation
Below is a complete skeleton plugin that implements the ICourierPlugin interface. Use this as a starting point for your own implementation.
import { ICourierPlugin } from '@app/courier/interfaces/courier-plugin.interface';
import { ITaskData } from '@app/courier/interfaces/task-data.interface';
import { IIntegrationResult } from '@app/courier/interfaces/integration-result.interface';
import { ITrackingInfo } from '@app/courier/interfaces/tracking-info.interface';
import { IWebhookPayload } from '@app/courier/interfaces/webhook-payload.interface';
import axios, { AxiosInstance } from 'axios';
import * as crypto from 'crypto';
export class MyCourierPlugin implements ICourierPlugin {
readonly name = 'my-courier';
readonly version = '1.0.0';
private httpClient: AxiosInstance;
private apiKey: string;
private webhookSecret: string;
private readonly SUPPORTED_FEATURES = new Set([
'realtime-tracking',
'cancellation',
]);
async initialize(config: Record<string, any>): Promise<void> {
this.apiKey = config.apiKey;
this.webhookSecret = config.webhookSecret;
this.httpClient = axios.create({
baseURL: config.baseUrl || 'https://api.mycourier.com/v1',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
timeout: 10000,
});
}
async createTask(taskData: ITaskData): Promise<IIntegrationResult> {
try {
const response = await this.httpClient.post('/deliveries', {
pickup: {
address: taskData.pickupAddress,
latitude: taskData.pickupLat,
longitude: taskData.pickupLng,
contact: taskData.pickupContact,
},
dropoff: {
address: taskData.dropoffAddress,
latitude: taskData.dropoffLat,
longitude: taskData.dropoffLng,
contact: taskData.dropoffContact,
},
packages: taskData.packages,
notes: taskData.notes,
});
return {
success: true,
externalTrackingId: response.data.trackingId,
metadata: {
estimatedDelivery: response.data.estimatedDelivery,
},
};
} catch (error) {
return {
success: false,
error: error.response?.data?.message || error.message,
};
}
}
async cancelTask(externalTrackingId: string): Promise<IIntegrationResult> {
try {
await this.httpClient.post(`/deliveries/${externalTrackingId}/cancel`);
return {
success: true,
externalTrackingId,
};
} catch (error) {
return {
success: false,
externalTrackingId,
error: error.response?.data?.message || error.message,
};
}
}
async getTracking(externalTrackingId: string): Promise<ITrackingInfo> {
const response = await this.httpClient.get(
`/deliveries/${externalTrackingId}/tracking`,
);
return {
externalTrackingId,
status: this.mapStatus(response.data.status),
location: response.data.driverLocation
? {
lat: response.data.driverLocation.latitude,
lng: response.data.driverLocation.longitude,
}
: undefined,
estimatedDelivery: response.data.eta,
updatedAt: response.data.lastUpdate,
};
}
async validateWebhook(payload: any, signature?: string): Promise<boolean> {
if (!signature) {
return false;
}
const expectedSignature = crypto
.createHmac('sha256', this.webhookSecret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature),
);
}
async parseWebhook(payload: any): Promise<IWebhookPayload> {
return {
externalTrackingId: payload.delivery_id,
event: payload.event_type,
status: this.mapStatus(payload.status),
location: payload.location
? { lat: payload.location.lat, lng: payload.location.lng }
: undefined,
timestamp: new Date(payload.occurred_at),
};
}
supportsFeature(feature: string): boolean {
return this.SUPPORTED_FEATURES.has(feature);
}
async healthCheck(): Promise<{ healthy: boolean; message?: string; details?: any }> {
try {
const start = Date.now();
await this.httpClient.get('/health');
const latency = Date.now() - start;
return {
healthy: true,
message: 'API is reachable',
details: { latencyMs: latency },
};
} catch (error) {
return {
healthy: false,
message: `API health check failed: ${error.message}`,
};
}
}
/** Map courier-specific statuses to Deliverty Hub statuses */
private mapStatus(externalStatus: string): string {
const statusMap: Record<string, string> = {
'created': 'NEW',
'assigned': 'ASSIGNED',
'picked_up': 'IN_TRANSIT',
'in_transit': 'IN_TRANSIT',
'delivered': 'COMPLETED',
'cancelled': 'CANCELLED',
'failed': 'FAILED',
};
return statusMap[externalStatus] || 'UNKNOWN';
}
}
For an overview of how plugins fit into the system architecture, see Plugin Architecture. For instructions on packaging and delivering your plugin, see Deployment.