A standalone Pick Up Drop Off (PUDO) platform integration API. Connect your courier, e-commerce, or logistics system directly to our network of local drop-off points.
This documentation aims to provide all the information you need to work with our API.
All external API requests must be signed using HMAC-SHA256. Three headers are required on every request:
| Header | Description |
|---|---|
X-PUDO-Key |
Your API key (starts with PUDO_) |
X-PUDO-Timestamp |
Current Unix timestamp in seconds (requests older than 5 minutes are rejected) |
X-PUDO-Signature |
HMAC-SHA256 signature (see below) |
The signature is computed as:
HMAC-SHA256( timestamp + raw_request_body, api_secret )
timestamp is the same value you send in X-PUDO-Timestampraw_request_body is the raw JSON string (or empty string for GET requests)api_secret is the secret shown once on your integration detail pageconst crypto = require('crypto');
const timestamp = Math.floor(Date.now() / 1000).toString();
const body = JSON.stringify({ pudo_point_id: 1, customer_name: "John Doe" /* ... */ });
const signature = crypto
.createHmac('sha256', YOUR_API_SECRET)
.update(timestamp + body)
.digest('hex');
fetch('https://your-domain.com/api/v1/packages/ingest', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-PUDO-Key': YOUR_API_KEY,
'X-PUDO-Timestamp': timestamp,
'X-PUDO-Signature': signature,
},
body,
});
$timestamp = (string) time();
$body = json_encode($payload);
$signature = hash_hmac('sha256', $timestamp . $body, $apiSecret);
$response = Http::withHeaders([
'X-PUDO-Key' => $apiKey,
'X-PUDO-Timestamp' => $timestamp,
'X-PUDO-Signature' => $signature,
])->post('/api/v1/packages/ingest', $payload);
Note: You can retrieve your API key and secret from the Integration Partners page in the admin panel after your account is approved.
Find all active PUDO points. Use query parameters to filter by branch or cold storage availability.
Filter by branch reference.
Filter by cold storage availability.
curl --request GET \
--get "https://pudo.2pointapp.com/api/v1/pudo-points/all?branch_ref=accra-main&cold_storage=1" \
--header "X-PUDO-Key: {YOUR_AUTH_KEY}" \
--header "X-PUDO-Timestamp: required Current Unix timestamp in seconds. Example: 1709845200" \
--header "X-PUDO-Signature: required HMAC-SHA256(timestamp + raw_body, api_secret). Example: a3f5c2..." \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"status": "success",
"data": [
{
"id": 1,
"name": "Shell Station East Legon",
"address": "Boundary Road, Accra",
"lat": "5.63410000",
"lng": "-0.15230000",
"has_cold_storage": true,
"currency": "GHS",
"price": 5.00,
"operating_hours": {
"mon": "08:00-20:00",
"tue": "08:00-20:00",
}
}
]
Find active PUDO points near a location or within a branch.
Latitude of the search center.
Longitude of the search center.
Search radius in KM. Default: 10.
Filter by branch reference.
Filter by cold storage availability.
curl --request GET \
--get "https://pudo.2pointapp.com/api/v1/pudo-points?lat=5.6037&lng=-0.187&radius=25&branch_ref=accra-main&cold_storage=1" \
--header "X-PUDO-Key: {YOUR_AUTH_KEY}" \
--header "X-PUDO-Timestamp: required Current Unix timestamp in seconds. Example: 1709845200" \
--header "X-PUDO-Signature: required HMAC-SHA256(timestamp + raw_body, api_secret). Example: a3f5c2..." \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"status": "success",
"data": [
{
"id": 1,
"name": "Shell Station East Legon",
"address": "Boundary Road, Accra",
"lat": "5.63410000",
"lng": "-0.15230000",
"distance": 2.4,
"has_cold_storage": true
"currency": "GHS",
"price": 5.00
}
]
}
The ID of the PUDO point.
curl --request GET \
--get "https://pudo.2pointapp.com/api/v1/pudo-points/1" \
--header "X-PUDO-Key: {YOUR_AUTH_KEY}" \
--header "X-PUDO-Timestamp: required Current Unix timestamp in seconds. Example: 1709845200" \
--header "X-PUDO-Signature: required HMAC-SHA256(timestamp + raw_body, api_secret). Example: a3f5c2..." \
--header "Content-Type: application/json" \
--header "Accept: application/json" cache-control: no-cache, private
content-type: application/json
access-control-allow-origin: *
{
"message": "Invalid API key"
}
Retrieve packages that have been ingested into the PUDO system by your integration.
Filter by package status (e.g., pending, dropped, ready, picked_up).
Limit the number of results. Default: 15.
curl --request GET \
--get "https://pudo.2pointapp.com/api/v1/packages?status=ready&limit=10" \
--header "X-PUDO-Key: {YOUR_AUTH_KEY}" \
--header "X-PUDO-Timestamp: required Current Unix timestamp in seconds. Example: 1709845200" \
--header "X-PUDO-Signature: required HMAC-SHA256(timestamp + raw_body, api_secret). Example: a3f5c2..." \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"status": "success",
"data": {
"current_page": 1,
"data": [
{
"id": 1,
"tracking_number": "PUDO-12345678",
"external_booking_ref": "BK-9921",
"pudo_point_id": 1,
"customer_name": "John Doe",
"status": "pending",
"payment_status": "unpaid",
"created_at": "2024-03-08T10:00:00.000000Z"
}
],
"total": 1
}
}
Retrieve the details and statuses for a specific package using its tracking number.
The PUDO tracking number of the package.
curl --request GET \
--get "https://pudo.2pointapp.com/api/v1/packages/PUDO-12345678" \
--header "X-PUDO-Key: {YOUR_AUTH_KEY}" \
--header "X-PUDO-Timestamp: required Current Unix timestamp in seconds. Example: 1709845200" \
--header "X-PUDO-Signature: required HMAC-SHA256(timestamp + raw_body, api_secret). Example: a3f5c2..." \
--header "Content-Type: application/json" \
--header "Accept: application/json" {
"status": "success",
"data": {
"id": 1,
"tracking_number": "PUDO-12345678",
"external_booking_ref": "BK-9921",
"pudo_point_id": 1,
"customer_name": "John Doe",
"status": "pending",
"payment_status": "unpaid",
"created_at": "2024-03-08T10:00:00.000000Z"
}
}
Submit a package from your system to a PUDO point. The package will start in 'pending' status.
curl --request POST \
"https://pudo.2pointapp.com/api/v1/packages/ingest" \
--header "X-PUDO-Key: {YOUR_AUTH_KEY}" \
--header "X-PUDO-Timestamp: required Current Unix timestamp in seconds. Example: 1709845200" \
--header "X-PUDO-Signature: required HMAC-SHA256(timestamp + raw_body, api_secret). Example: a3f5c2..." \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"pudo_point_id\": 1,
\"external_booking_ref\": \"BK-9921\",
\"customer_name\": \"John Doe\",
\"customer_email\": \"john@example.com\",
\"customer_phone\": \"+233240000000\",
\"cold_storage\": false,
\"notes\": \"Fragile electronics\",
\"payment_status\": \"unpaid\",
\"payment_method\": \"cod\",
\"amount_to_collect\": \"15.50\"
}"
{
"status": "success",
"message": "Package ingested successfully",
"data": {
"tracking_number": "PUDO-12345678",
"status": "pending"
}
}
Register a URL to receive status updates when a package status changes. We will POST to this URL with an HMAC signature.
curl --request POST \
"https://pudo.2pointapp.com/api/v1/webhooks/register" \
--header "X-PUDO-Key: {YOUR_AUTH_KEY}" \
--header "X-PUDO-Timestamp: required Current Unix timestamp in seconds. Example: 1709845200" \
--header "X-PUDO-Signature: required HMAC-SHA256(timestamp + raw_body, api_secret). Example: a3f5c2..." \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"webhook_url\": \"https:\\/\\/api.mycompany.com\\/pudo\\/webhook\"
}"
{
"status": "success",
"message": "Webhook registered successfully",
"data": {
"webhook_url": "https://api.mycompany.com/pudo/webhook"
}
}
curl --request POST \
"https://pudo.2pointapp.com/api/v1/webhooks/logistics/status" \
--header "X-PUDO-Key: {YOUR_AUTH_KEY}" \
--header "X-PUDO-Timestamp: required Current Unix timestamp in seconds. Example: 1709845200" \
--header "X-PUDO-Signature: required HMAC-SHA256(timestamp + raw_body, api_secret). Example: a3f5c2..." \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"event\": \"architecto\",
\"tracking_number\": \"architecto\",
\"status\": \"architecto\",
\"eta_to_pickup_minutes\": 16,
\"eta_to_dropoff_minutes\": 16
}"