RapidXpkDevelopers

RapidXpk Integration API

Send delivery orders from your store or system, follow every parcel to the door, and get told the moment its status changes.

https://api.rapidxpk.comv1JSON over HTTPSUpdated 22 Oct 2026

Overview

Every request goes to https://api.rapidxpk.com/v1/… with your key in a header. You send each order with your own reference in external_id. RapidXpk answers with its order number (RXK-123456) and a public tracking link you can share with your customer.

Sending the same external_id twice never makes a second parcel: you get the first order back. That makes retries after a timeout safe.

Check the service
curl https://api.rapidxpk.com/v1/health

Keys and auth

Send your key in the X-API-Key header on every call. RapidXpk issues two kinds of key when you are onboarded:

KeyWhat happens to orders
rxp_test_…Accepted and visible to RapidXpk, never dispatched. Use it while you build.
rxp_live_…Real orders: picked up, delivered and billed.

Keep keys on your server. Never put them in a website or mobile app. If a key leaks, ask RapidXpk to revoke it and issue a new one.

Order statuses

received→packed→dispatched→delivered
StatusMeaning
receivedWe have the order. RapidXpk confirms it next.
packedConfirmed and ready for a rider.
dispatchedOut for delivery with a rider.
deliveredHanded to the customer.
failed_attemptThe rider reached the door and could not deliver. reason says why. RapidXpk tries again or returns the parcel.
rtoReturn to origin: the parcel is coming back to you.
returnedThe parcel is back with us or with you.
cancelledCancelled before the rider took it.

Charges follow attempts at the door. An order cancelled before it went out costs nothing.

Create an order

POST/v1/ordersOne order. Returns 201 when created, 200 when that external_id already exists.
FieldTypeNotes
external_idrequiredstringYour order reference. Unique per account; makes retries safe.
customer.namerequiredstring
customer.phonerequiredstringPakistani mobile. 03XXXXXXXXX, +92… and 92… are accepted.
customer.addressrequiredstringFull address. customer.area is accepted instead.
customer.lat, customer.lngnumberOptional location hint. RapidXpk still confirms the exact door pin before dispatch.
itemsarray[{ "sku", "name", "qty" }]. Quantities are added up; several items are listed by name on the parcel.
payment_methodstringcod (default) or prepaid.
cod_amountnumberRupees the rider collects. Ignored for prepaid.
order_valuenumberGoods value. Used as the COD amount when cod_amount is missing.
notesstringShown to the rider: gate code, landmark, call first.
servicestringOptional service name agreed with RapidXpk.
Request
curl -X POST https://api.rapidxpk.com/v1/orders \
  -H "X-API-Key: rxp_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "SHOP-10482",
    "customer": {
      "name": "Sana Malik",
      "phone": "03001234567",
      "address": "House 12, Street 5, DHA Phase 6, Karachi"
    },
    "items": [{ "sku": "VC-30", "name": "Vitamin C Serum 30ml", "qty": 2 }],
    "payment_method": "cod",
    "cod_amount": 4900,
    "notes": "Call on arrival"
  }'
Response 201
{
  "order_id": "RXK-482915",
  "external_id": "SHOP-10482",
  "status": "received",
  "mode": "live",
  "tracking_url": "https://rapidxpk.com/track.html?tn=RXK-482915",
  "created_at": "2026-10-22T07:15:02Z"
}

Create in bulk

POST/v1/orders/bulkUp to 100 orders per request, same fields as above. Answers 207 with one result per order, in the order you sent them.
Request body
{ "orders": [ { "external_id": "SHOP-10483", … }, { "external_id": "SHOP-10484", … } ] }
Response 207
{
  "count": 2,
  "results": [
    { "index": 0, "ok": true,  "result": { "order_id": "RXK-482916", … } },
    { "index": 1, "ok": false, "result": { "error": "invalid_phone", "field": "customer.phone", … } }
  ]
}

Resend only the ones with "ok": false after fixing them. The others are already in.

Get one order

GET/v1/orders/{order_id or external_id}
GET/v1/orders?external_id=SHOP-10482
Response 200
{
  "order_id": "RXK-482915",
  "external_id": "SHOP-10482",
  "status": "failed_attempt",
  "reason": "Customer not home",
  "attempts": 1,
  "packed_at": "2026-10-22T08:02:11Z",
  "dispatched_at": "2026-10-22T10:40:05Z",
  "delivered_at": null,
  "returned_at": null,
  "updated_at": "2026-10-22T13:21:47Z",
  "cod": { "amount": 4900, "collected": false, "settled": false },
  "tracking_url": "https://rapidxpk.com/track.html?tn=RXK-482915"
}

cod.collected turns true when the rider's cash reaches RapidXpk. cod.settled turns true when it is paid out to you.

Sync changes

If you cannot receive webhooks, poll for every order that changed since your last call.

GET/v1/orders?updated_since=2026-10-22T00:00:00Z&after_id=0&limit=200limit is 1 to 500 (default 200). Results come oldest change first.
Response 200
{
  "orders": [ { /* same shape as Get one order */ } ],
  "count": 200,
  "next_since": "2026-10-22T09:14:03.551210Z",
  "next_id": 48211,
  "has_more": true
}

Call again with updated_since=next_since and after_id=next_id while has_more is true. Save both values for your next poll. Every five minutes is plenty.

Cancel an order

POST/v1/orders/{order_id or external_id}/cancelBody { "reason": "Customer changed mind" } is optional.
AnswerWhen
200 "status": "cancelled"Cancelled. Calling it again answers the same with "already": true.
409 too_lateThe rider already has the parcel. Call RapidXpk to recall it.
404 not_foundNo order with that reference on your account.

Webhooks

Give us one public HTTPS address and we post every status change to it, normally within a few minutes.

POST/v1/webhooksBody { "url": "https://yourshop.com/rapidxpk/hook" }. Answers 201 with a signing secret, shown only once. Setting a new URL replaces the old one and issues a new secret.
GET/v1/webhooksThe current URL and when it last succeeded or failed.
DELETE/v1/webhooksStops deliveries.

The address must be https:// on port 443 and reachable from the internet. Local and private addresses are refused.

Events

order.received, order.packed, order.dispatched, order.delivered, order.failed_attempt, order.rto, order.returned, order.cancelled.

Delivery body
{
  "event": "order.delivered",
  "order_id": "RXK-482915",
  "external_id": "SHOP-10482",
  "status": "delivered",
  "previous_status": "dispatched",
  "reason": null,
  "attempts": 2,
  "occurred_at": "2026-10-23T12:05:40Z",
  "packed_at": "2026-10-22T08:02:11Z",
  "dispatched_at": "2026-10-23T10:12:09Z",
  "delivered_at": "2026-10-23T12:05:40Z",
  "returned_at": null,
  "cod": { "amount": 4900, "collected": false, "settled": false },
  "tracking_url": "https://rapidxpk.com/track.html?tn=RXK-482915"
}
HeaderValue
X-RapidXpk-EventThe event name, same as event in the body.
X-RapidXpk-DeliveryA delivery number. A retry keeps the same number, so you can ignore repeats.
X-RapidXpk-TimestampUnix seconds when we signed it.
X-RapidXpk-Signaturesha256= followed by HMAC-SHA256 of timestamp + "." + raw body, keyed with your secret.

Answer quickly

Reply with any 2xx within 12 seconds, then do your own work. Anything else counts as a failure and we try again after 1, 2, 4, 8 minutes and so on, up to every 6 hours, for 12 attempts in all. GET /v1/webhooks shows the last error, and you can always catch up with Sync changes.

Verify a delivery

Check the signature against the raw request body before you trust it, and refuse anything signed more than 5 minutes ago.

Node.js
const crypto = require('crypto');

// rawBody: the exact bytes we sent, before JSON parsing
function isFromRapidXpk(headers, rawBody, secret) {
  const ts = headers['x-rapidxpk-timestamp'];
  const sig = headers['x-rapidxpk-signature'] || '';
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(ts + '.' + rawBody).digest('hex');
  return sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
PHP
$raw = file_get_contents('php://input');
$ts  = $_SERVER['HTTP_X_RAPIDXPK_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_RAPIDXPK_SIGNATURE'] ?? '';
$ok  = abs(time() - (int)$ts) <= 300
    && hash_equals('sha256=' . hash_hmac('sha256', $ts . '.' . $raw, $secret), $sig);
if (!$ok) { http_response_code(401); exit; }
http_response_code(200);   // then process $raw

Shopify

No code needed. Your RapidXpk portal gives you a personal webhook address under Connect orders → Shopify.

  1. In Shopify admin open Settings → Notifications → Webhooks and choose Create webhook.
  2. Event: Order creation. Format: JSON. Paste the address from your portal.
  3. Copy the signing secret Shopify shows under the webhooks list into the portal (Connect orders → Store settings). We use it to check every order really came from your store.
  4. Place a test order. It appears in the portal under Connect orders → Incoming orders, with product pictures, ready for RapidXpk to confirm.

Odoo 18

Odoo's built-in webhook action cannot send headers, so it uses an address with your key in the path:

POST/v1/orders/odoo/{your rxp_ key}

Odoo sends only the sale order number. Save an Odoo API user in the portal (Connect orders → Odoo) and we read the full order from your Odoo and create the delivery. Without it the call is accepted with 202 and the answer explains the next step. Your RapidXpk contact has the full Odoo setup guide.

Errors

Every error has the same shape, and message is written to be shown to a person:

Example 400
{ "error": "missing_field", "field": "customer.name", "message": "customer.name is required." }
HTTPerrorWhat to do
400missing_fieldAdd the field named in field.
400invalid_phoneSend an 11-digit Pakistani mobile, 03XXXXXXXXX.
400invalid_fieldqty must be a whole number; amounts and coordinates plain numbers.
400invalid_urlWebhook address must be public HTTPS on port 443.
400too_manyBulk takes at most 100 orders per request.
400invalid_payloadThe body is not the expected JSON. Bulk needs { "orders": [ … ] }.
400invalid_timestampupdated_since must be ISO 8601, for example 2026-10-22T10:00:00Z.
400missing_referencePut an order number in the path or use ?external_id=.
401missing_api_key, invalid_api_keyCheck the X-API-Key header and that the key is not revoked.
404not_foundNo order with that reference on your account.
405method_not_allowedUse POST to create orders.
409too_lateThe rider has the parcel; it can no longer be cancelled here.
429too_many_attemptsToo many badly formed keys from your address. Wait 10 minutes.
501not_enabledStock levels are not switched on for your account.
502upstream_errorRetry shortly. Creating is safe to retry with the same external_id.
502odoo_unreachableWe could not read the order from your Odoo. Check the Odoo details saved in the portal.

Limits

WhatLimit
Orders per bulk request100
Results per sync page500 (default 200)
Webhook answer time12 seconds
Webhook retries12 attempts, backing off to every 6 hours
Badly formed keys30 from one address within 10 minutes blocks that address for 10 minutes
COD amountRs 0 to Rs 10,000,000 per order
Stock levels (/v1/inventory)Not switched on yet. Ask your RapidXpk contact.