Post deliveries to Govza from your system
Price a trip, create a delivery, follow it to the door — over plain HTTPS and JSON, authenticated with a key the customer issues themselves.
1. Get a key
Keys are created by the Govza customer whose deliveries you will be placing, in their dashboard at run.govza.app/api-keys → API-ключи → Создать ключ. Every request you make with that key acts as that customer: the orders are theirs, billed to them, and visible in their order list.
The key is shown once. Govza stores only a hash of it, so it cannot be recovered — if it is lost, the customer creates a new one and revokes the old.
Check it works before writing anything else:
curl https://api.govza.app/v1/partner/ping \
-H "Authorization: Bearer gvz_<key id>_<secret>"
The response names the account the key acts as. A 401 means the key is
wrong, revoked, or its account can no longer create orders.
2. Understand addresses first
This is the part integrations get wrong, so it is worth reading before you write any code. Govza does not geocode. There is no address lookup, no spelling correction and no fuzzy matching behind this API. Every address you send must already be structured and must already carry coordinates.
| Field | Required | Notes |
|---|---|---|
latitude |
Yes | Decides routing and price. −90…90. |
longitude |
Yes | −180…180. |
area |
Yes | City or locality. |
street |
Yes | Street name, without the house number. |
building |
Yes | House or building number, as text — "12к3" is fine. |
apartment, entrance, floor |
No |
Whole numbers only. "12A" is rejected rather than silently
dropped — put it in instructions.
|
intercom, instructions |
No | Free text shown to the courier. |
The coordinates are authoritative for routing and pricing; the text is what the courier reads on the way. Keep them consistent — nothing on our side reconciles the two.
If a coordinate falls outside an area Govza serves, the order is rejected at creation rather than accepted and left undeliverable.
3. Price the trip
curl -X POST https://api.govza.app/v1/partner/quotes \
-H "Authorization: Bearer $GOVZA_KEY" \
-H "Content-Type: application/json" \
-d '{
"pickup": {
"address": {
"latitude": 43.3169, "longitude": 45.6981,
"area": "Грозный", "street": "проспект Путина", "building": "1"
},
"contact": { "value": "+79280000001", "name": "Склад" }
},
"dropoffs": [{
"address": {
"latitude": 43.3200, "longitude": 45.7000,
"area": "Грозный", "street": "улица Мира", "building": "12",
"apartment": 4, "entrance": 2, "floor": 3
},
"contact": { "value": "+79280000002", "name": "Получатель" }
}]
}'
You get back a price, a quote_id and an
expires_at. Quotes last five minutes.
4. Create the delivery
Same body, plus your own external_order_id and — if you want the price you
were just quoted — the quote_id.
curl -X POST https://api.govza.app/v1/partner/orders \
-H "Authorization: Bearer $GOVZA_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_order_id": "SHOP-10432",
"quote_id": "<quote_id from step 3>",
"payment_type": "cash",
"payment_amount": 1500,
"comment": "Позвонить за 10 минут",
"pickup": { "...": "as above" },
"dropoffs": [{ "...": "as above" }]
}'
The response carries the Govza order_code, the final
price, and a tracking_url you can forward to the recipient.
Retries are safe
external_order_id is the idempotency key. If you retry a create after a
timeout, you get 200 with the original order instead of
201 with a second courier on the road. Use an id from your own system and
never reuse one for a different delivery.
If the quote expired
A quote_id is honoured only while every leg of it is still valid.
Otherwise the trip is re-priced automatically and the response carries the new
price — always read the price back from the create response rather than
assuming the quote held.
5. Follow the delivery
Poll:
curl https://api.govza.app/v1/partner/orders/SHOP-10432 \
-H "Authorization: Bearer $GOVZA_KEY"
Statuses are new → assigned → in_progress →
delivered, or cancelled.
Webhooks
If the key has a webhook URL configured, Govza POSTs each status change to
it. Respond 2xx to acknowledge; anything else is retried with exponential
backoff for up to eight attempts.
Every request carries a signature header:
X-Govza-Signature: t=1723377600,v1=9f86d081884c7d65...
v1 is HMAC-SHA256(webhook_secret, "<t>.<raw body>"),
hex-encoded. Verify against the raw body, before any JSON parsing, and
reject requests whose t is more than a few minutes old — that timestamp is
what stops a captured callback being replayed later.
import crypto from 'node:crypto';
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(',').map((kv) => kv.split('='))
);
const expected = crypto
.createHmac('sha256', secret)
.update(`${parts.t}.${rawBody}`, 'utf8')
.digest('hex');
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (age > 300) return false;
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1)
);
}
Cancelling
curl -X POST https://api.govza.app/v1/partner/orders/SHOP-10432/cancel \
-H "Authorization: Bearer $GOVZA_KEY" \
-H "Content-Type: application/json" \
-d '{ "reason": "customer_changed_mind" }'
There is no edit. To change a delivery, cancel it and create a new one.
Errors
Validation failures return 400 and list every offending
field at once, so one response is enough to fix the request:
{
"success": false,
"error": {
"id": "validation.partner_payload_invalid",
"message": "dropoffs.0.address.latitude: latitude is required",
"details": "[{\"field\":\"dropoffs.0.address.latitude\",\"message\":\"latitude is required\"}]"
}
}
| Status | Meaning |
|---|---|
400 |
Payload rejected. See error.details. |
401 |
Key missing, malformed, revoked, or its account cannot create orders. |
404 |
No order for that external_order_id. |
409 |
A concurrent request is creating this order. Retry shortly. |
429 |
Rate limit for this key exceeded. Back off and retry. |
Full endpoint and schema detail is in the API reference.