Webhook Events & Integration
This page documents both the public webhook consumption rules used by the
public /api payment surface and the deployment-specific merchant/developer
webhook flow used by private dashboard tooling.
Overview
Webhook delivery lets your system react to payment, escrow, disbursement, and merchant event changes without aggressive polling.
Contract Split
There are two webhook surfaces in the current codebase:
the default public
/apipayment surface, which emits outbound webhook deliveries but does not expose public webhook endpoint management routesa deployment-specific merchant/developer webhook flow, typically managed from private dashboard tooling rather than the default public
/apirouter
Public Contract Notes
For the default public paymentAPI router:
webhook consumption is part of the public integration surface
webhook endpoint creation is not currently exposed as a default public
/apiroute
If your deployment exposes dashboard or private tooling for webhook endpoint
registration, treat that as deployment-specific configuration rather than part of
the guaranteed public /api contract.
Some deployments also expose private developer tooling to create endpoints, set or rotate a signing secret, and trigger a manual test delivery. In those flows, the full signing secret should be treated as write-only and copied when it is created or rotated.
Private Merchant/Developer Management
When the private merchant/developer webhook flow is enabled, the current code supports routes such as:
POST /users/webhooksto create an endpointGET /users/merchant/webhooksto list configured endpointsPATCH /users/merchant/webhooks/{webhook_id}to update URL, description, subscribed events, active state, or signing secretPOST /users/merchant/webhooks/{webhook_id}/testto send a real test delivery using the first subscribed eventDELETE /users/merchant/webhooks/{webhook_id}to remove an endpoint
Secret Lifecycle
For the merchant/developer webhook flow:
create accepts
url, optionaldescription, one or moreevents, and an optional customsecretif
secretis omitted, Piaxis generates one server-sidethe full signing secret is returned only once, immediately after create or secret rotation
later list/profile responses expose only metadata such as
has_secretandsecret_hint
Merchant/Developer Delivery Pattern
The merchant/developer webhook model currently posts JSON with this envelope:
{
"event": "payment.succeeded",
"data": {
"test": true,
"merchant_id": "6c15f7e8-335a-4c62-a993-fb06a27b7787",
"merchant_name": "Demo Merchant",
"payment_id": "3a0da2c0-a8b2-4c76-9f40-1dbf7ee03dcf",
"reference": "test_9f7d1bd7b5aa",
"currency": "UGX",
"amount": 150000,
"status": "succeeded"
},
"timestamp": "2026-04-14T10:30:00.000000",
"webhook_id": "0e4102fa-3119-42aa-8a9c-d92ef4fa83af"
}
The manual test-delivery endpoint uses the first subscribed event on the
webhook and marks the event payload with "test": true.
Public Payment Helper Delivery Pattern
The shared helper used by the public payment API posts JSON with this envelope:
{
"data": {
"event": "disbursement.status_updated",
"disbursement_id": "b6b93a8b-2c81-44bf-9d98-9a43f725b90f",
"status": "completed"
},
"timestamp": "2026-01-15T10:30:00+00:00"
}
Headers
Content-Type: application/json
User-Agent: piaxis-Webhook/1.0
X-piaxis-Signature: <hex_hmac_sha256_if_secret_is_configured>
X-piaxis-Event: <event_name>
Both the merchant/developer flow and the public payment helper currently use these headers.
Merchant/Developer Event Catalog
The current merchant/developer webhook event set is:
order.createdorder.updatedorder.completedpayment.succeededpayment.failedrefund.processedescrow.releaseddispute.openeddispute.closed
Signature Verification
When a webhook secret is configured, Piaxis computes:
HMAC SHA256
over the raw JSON payload body
using the shared webhook secret
Python example
import hashlib
import hmac
def verify_signature(raw_body: bytes, header_value: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header_value)
Consumer Requirements
Your webhook endpoint should:
accept HTTP
POSTrequestsread the raw body before mutating it
verify
X-piaxis-Signaturewhen a secret is configureduse
X-piaxis-Eventor the event field in the JSON body for routingtreat delivery as at-least-once and process idempotently
return a
2xxresponse after durable handling
Public Helper Event Types
The public payment surface may deliver event names inside data.event. Event
names vary by subsystem. Examples observed in the current public surface include:
disbursement.status_updateddisbursement.cancelled
Merchant/developer webhook deliveries use the top-level event field instead.
If your deployment consumes both surfaces, route by payload.get("event")
first and fall back to payload.get("data", {}).get("event").
Minimal Handler Example
from fastapi import FastAPI, Header, HTTPException, Request
app = FastAPI()
WEBHOOK_SECRET = "your_webhook_secret"
@app.post("/webhooks/piaxis")
async def handle_piaxis_webhook(
request: Request,
x_piaxis_signature: str | None = Header(default=None),
):
raw_body = await request.body()
if x_piaxis_signature:
if not verify_signature(raw_body, x_piaxis_signature, WEBHOOK_SECRET):
raise HTTPException(status_code=401, detail="Invalid webhook signature")
payload = await request.json()
event_name = payload.get("event") or payload.get("data", {}).get("event")
if event_name == "payment.succeeded":
# update your internal payment tracking here
pass
return {"ok": True}
Operational Guidance
Store webhook deliveries with your own request id or event id when present.
Make webhook handlers idempotent.
Reconcile with polling when a webhook is missed.
- Never assume a richer payload than the current event requires.
If your deployment exposes private dashboard tooling, use the returned delivery metadata such as last event, HTTP status, error text, and response preview for debugging failed endpoints.