New: Application API docs
Pick the API you are reading here — the Merchant API for your own store, the Application API for partner apps.
Everything you need to integrate with the Gini v2 Third-Party API.
Configure and manage your single outbound webhook endpoint — the HTTPS URL Gini POSTs real-time product and order events to. The four management endpoints below are HMAC-signed like the rest of the API. The deliveries Gini sends you are not signed: the Authorization header you configure here is what proves they came from Gini. The stored auth_value is never returned, and actions made through the Third-Party API itself never fire events.
Returns the merchant's current webhook configuration, or a null data payload when no webhook is configured. The stored auth_value credential is deliberately never included.
/api/v2.0/third-party/webhook
{
"data": {
"url": "https://your-server.com/webhook",
"auth_type": "bearer",
"created_at": "2026-02-10T14:00:00+03:00",
"updated_at": "2026-02-10T14:00:00+03:00"
}
}
Creates or overwrites the merchant's webhook configuration. There is exactly one webhook per merchant (upserted on client_id), so calling this again replaces the existing config in place.
/api/v2.0/third-party/webhook
| Field | Type | Required | Description |
|---|---|---|---|
url
|
string | Required | The endpoint Gini will POST events to. Must be a valid HTTPS URL (url:https) and pass the SSRF safety check: no localhost aliases, no raw-IP hosts (must be a domain name), and must not resolve to a private/reserved/loopback IP. |
auth_type
|
string | Required | Authorization scheme Gini uses when calling your endpoint. One of: basic, bearer, none. |
auth_value
|
string | Optional | The raw credential Gini sends in the Authorization header — a token for bearer, or username:password for basic. Gini base64-encodes the basic form for you, so do not pre-encode it. Required unless auth_type is none; nullable and max 1024 characters. Never returned by any endpoint. |
{
"url": "https://your-server.com/webhook",
"auth_type": "bearer",
"auth_value": "your-secret-token"
}
{
"data": {
"url": "https://your-server.com/webhook",
"auth_type": "bearer",
"message": "Webhook configuration updated successfully."
}
}
Removes the merchant's webhook configuration. Returns 404 when there is nothing to delete.
/api/v2.0/third-party/webhook
{
"data": {
"message": "Webhook configuration removed."
}
}
Synchronously fires a synthetic 'test' event at the configured webhook URL and reports the raw HTTP outcome. Unlike queued event deliveries this never retries — it returns the immediate result so you can verify your endpoint.
/api/v2.0/third-party/webhook/test
{
"data": {
"success": true,
"status_code": 200,
"message": "Test webhook sent successfully."
}
}
Every event is a POST of a JSON object carrying an event key plus the subject of the
event. These are the six events Gini sends.
| Event | Fires when | Body |
|---|---|---|
| order_created | A customer places an order with you. | { event, order, user } |
| order_updated | An order of yours changes status. | { event, order } |
| product_created | One of your products is created. | { event, product } |
| product_updated | One of your products is edited. | { event, product } |
| product_deleted | One of your products is deleted. | { event, product } |
| product_bulk_delete_finished | A bulk delete you sent to POST /products/bulk-delete has finished. | { event, bulk_delete } |
{
"event": "order_created",
"order": {
"id": 5021,
"number": "5021",
"status": "pending",
"client": {
"id": 42
},
"items": []
},
"user": {
"id": 918,
"name": "Ahmed",
"phone": "07700000000",
"is_verified": 1
}
}
{
"event": "order_updated",
"order": {
"id": 5021,
"number": "5021",
"status": "delivered",
"client": {
"id": 42
},
"items": []
}
}
{
"event": "product_created",
"product": {
"id": 771,
"name": "My Product",
"sku": "SKU-771",
"price": 15000,
"final_price": 13500,
"quantity": 10,
"category": {
"id": 3,
"name": "Electronics"
},
"discountable": {
"percentage": 10,
"code": "EID10"
}
}
}
Traffic runs in both directions, and the two directions are secured differently. Requests you send Gini are signed. Deliveries Gini sends you are not — they are authenticated by the credential you configured above, and nothing else.
| Direction | What proves the request is genuine |
|---|---|
| Your request to Gini | An HMAC-SHA256 signature over METHOD + PATH + BODY + TIMESTAMP, sent as X-SIGNATURE alongside X-API-KEY and X-TIMESTAMP. See Authentication. |
| Gini delivery to your endpoint | The Authorization header built from your auth_type and auth_value. There is no signature header and no timestamp header on a delivery. |
// The token you saved as auth_value, with auth_type set to bearer.
$expected = 'Bearer ' . config('services.gini.webhook_token');
// Constant-time compare — a plain === leaks the token one byte at a time.
if (! hash_equals($expected, (string) $request->header('Authorization'))) {
abort(401);
}
// Answer fast, process later: Gini gives up after 10 seconds.
ProcessGiniWebhook::dispatch($request->json()->all());
return response()->noContent();