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.

TypeScript
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)

ParameterTypeDescription
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.

Example
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)

ParameterTypeDescription
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)

ParameterTypeDescription
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)

ParameterTypeDescription
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?)

ParameterTypeDescription
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)

ParameterTypeDescription
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)

ParameterTypeDescription
feature string The feature name to check (e.g., 'realtime-tracking', 'cancellation').

Returns: booleantrue 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.

Example
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.

my-courier.plugin.ts
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';
  }
}
Next Steps

For an overview of how plugins fit into the system architecture, see Plugin Architecture. For instructions on packaging and delivering your plugin, see Deployment.