This document describes how to call Livra integration endpoints from your app.
Use these headers for all Livra integration endpoints:
Content-Type: application/jsonx-api-key: <apiKey>x-signature: <hexHmac>x-signature must be HMAC-SHA256(rawRequestBody, apiSecret) encoded as lowercase hex (optionally prefixed with sha256=).
https://livra.mofavo.com/create_merchantPOST{
"merchant": {
"name": "Example Merchant LLC",
"state": "Dubai",
"city": "Dubai",
"street": "Example Street 1",
"phoneNumber": "+971500000000",
"zipcode": "00000",
"TRN": "100000000000003",
"CIN": 12345678
},
"sender": {
"name": "Example Sender LLC",
"state": "Dubai",
"city": "Dubai",
"street": "Business Bay",
"phoneNumber": "+971511111111"
},
"contract": {
"deliveryPartnerId": 10,
"deliveryFee": 12.5,
"exchangeFee": 4.25,
"cancellationFee": 3.0
}
}
merchant.CIN must be an 8-digit integer.contract.deliveryPartnerId must be a positive integer.{ "merchantId": <number> }https://livra.mofavo.com/create_orderPOST{
"products": [
{ "name": "string", "quantity": 1, "price": 12.5 },
{ "name": "string", "quantity": 2 }
],
"productsToRetrieve": [
{ "name": "string", "quantity": 1 },
{ "name": "", "quantity": 0 }
],
"merchantId": 1,
"deliveryPartnerId": 1,
"primaryName": "string",
"primaryPhone": "string",
"primaryPhone2": "",
"primaryStreet": "",
"primaryZone": "",
"primaryCity": "string",
"primaryState": "string",
"primaryZipcode": "",
"deliveryInstructions": "",
"amount": 12.5,
"allowOpen": true,
"isExchange": false,
"isFragile": false,
"callback_link": "https://your-app.example.com/livra/webhook"
}
callback_link is optional. Omit it or leave off to disable webhooks for that order.
products is required and must be a non-empty array.products[] item needs non-empty name, positive integer quantity, optional non-negative price.merchantId and deliveryPartnerId must be positive integers.primaryName, primaryPhone, primaryCity, primaryState must be non-empty.amount must be non-negative.allowOpen, isExchange, isFragile must be booleans.isExchange=true, productsToRetrieve is expected. Invalid/missing entries still proceed with fallback name "unknown".callback_link (optional): when present, Livra sends a signed POST to this URL whenever the order undergoes a meaningful status change (see Order status webhooks). Must be a valid absolute HTTP or HTTPS URL that accepts JSON POSTs from Livra’s infrastructure.{ "orderId": <number> }merchant_not_foundno_active_contractsender_not_foundhttps://livra.mofavo.com/update_orderPOSTPatch-style payload. Only orderId is required; all other fields are optional.
{
"orderId": 1234,
"products": [{ "name": "string", "quantity": 1 }],
"productsToRetrieve": [{ "name": "string", "quantity": 1 }],
"primaryName": "string",
"primaryPhone": "string",
"primaryPhone2": "",
"primaryStreet": "",
"primaryZone": "",
"primaryCity": "string",
"primaryState": "string",
"primaryZipcode": "",
"deliveryInstructions": "",
"amount": 12.5,
"allowOpen": true,
"isExchange": false,
"isFragile": false
}
orderId must exist.readyForPickUp; otherwise update is rejected.{ "orderId": <number> }order_not_foundorder_update_not_permittedhttps://livra.mofavo.com/change_requestPOSTx-api-key + x-signature flow as other endpoints{
"orderId": 1234,
"changes": [
{
"type": "PHONE_CHANGE",
"oldValue": "+971500000000",
"newValue": "+971511111111"
}
],
"comment": "Customer requested phone correction",
"makeRegular": false
}
orderId must be a positive integer.changes must be a non-empty array.type and must be one of:
PHONE_CHANGEPHONE2_CHANGEAMOUNT_CHANGEADDRESS_CHANGEALLOW_OPEN_CHANGEDELIVERY_DATE_CHANGEoldValue and newValue are optional and can be any JSON value.comment is optional string.makeRegular is optional boolean.inDepot or inTransit status.deliveryDate is set (exchange already happened), request creation is blocked.{ "ok": true }order_not_foundorder_status_not_eligible_for_change_requestexchange_already_completed_change_request_not_allowedno_changes_providedWhen you include callback_link on create order, Livra calls that URL with an outbound webhook on every meaningful change to the order.
Every request carries two headers that identify exactly what you are receiving:
X-Webhook-Type: advanced
X-Webhook-Version: 1
Use X-Webhook-Version to guard your parsing logic against future changes.
Current version: 1
The raw event payload as recorded by the platform. Each delivery represents one discrete change, with an explicit event name, a full snapshot of the current field values, and their previous values for comparison. Driver outcomes are separate driver.* events rather than a comment on an order event.
Full documentation: Livra Webhooks — Advanced
The following applies to all Livra webhooks.
Every request includes an X-Webhook-Signature header containing an HMAC-SHA256 of the raw request body, hex-encoded, using your Livra API secret (the same secret you use to sign requests to Livra).
Always verify this header before processing the payload.
Node.js
const crypto = require('crypto');
function verifySignature(secret, rawBody, signature) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
// Express example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-webhook-signature'];
if (!verifySignature(process.env.WEBHOOK_SECRET, req.body, sig)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
// process event...
res.sendStatus(200);
});
Python
import hmac, hashlib
def verify_signature(secret: str, raw_body: bytes, signature: str) -> bool:
expected = hmac.new(
secret.encode(),
raw_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
Go
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
func verifySignature(secret, signature string, body []byte) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}
Important: always read the raw request body for signature verification. Parsing the JSON first and re-serialising it may produce a different byte sequence and cause verification to fail.
Reply with any 2xx status code to acknowledge successful delivery. The response body is ignored.
If your endpoint returns a non-2xx status or does not respond within 10 seconds, the delivery is retried automatically.
| Attempt | Delay before retry |
|---|---|
| 1 | 30 seconds |
| 2 | 5 minutes |
| 3 | 30 minutes |
| 4 | 2 hours |
| 5 | 8 hours |
After 5 failed attempts the delivery is marked permanently failed and no further retries are made. The platform team can manually re-queue a delivery on request.
Each delivery has a unique UUID in the X-Webhook-ID header. Use it to deduplicate events if your endpoint receives the same delivery more than once.