Skip to content

Webhooks API

Complete reference for programmatic webhook subscription management. Use these endpoints to register HTTPS endpoints that receive JSON notifications when events occur in your tenant.

Inbound vs outbound

These endpoints configure outbound webhooks (Waqti → your server). They are not the URL that payment providers call into Waqti.

List Webhook Subscriptions

http
GET /v1/webhooks

Returns all webhook subscriptions for the authenticated tenant. Secrets are never included in list responses.

Example Request

bash
curl -X GET "https://api.waqti.sa/v1/webhooks" \
  -H "Authorization: Bearer 3|a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0" \
  -H "Accept: application/json"

Example Response

json
{
  "data": [
    {
      "id": 7,
      "name": "ERP sync",
      "url": "https://integrations.example.com/waqti/webhook",
      "events": [
        "purchase_order.approved",
        "invoice.paid",
        "vendor.updated"
      ],
      "is_active": true,
      "failure_count": 0,
      "last_triggered_at": "2026-03-28T09:12:33Z",
      "created_at": "2026-01-10T14:20:00Z"
    }
  ]
}

Create Webhook

http
POST /v1/webhooks

Creates a new subscription. Provide the URL to receive POST requests and the list of event types to subscribe to.

Request Body

FieldTypeRequiredDescription
namestringYesDisplay name (max 100 characters)
urlstringYesHTTPS endpoint that accepts application/json POST (max 500 characters)
eventsarrayYesOne or more event type strings (see Available events)

The signing secret used for HMAC verification is generated by Waqti and returned only in this response. It cannot be set in the request body and is not shown again on later GET or PUT responses—store it securely when you create the subscription.

Example Request

bash
curl -X POST "https://api.waqti.sa/v1/webhooks" \
  -H "Authorization: Bearer 3|a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Procurement automation",
    "url": "https://integrations.example.com/waqti/webhook",
    "events": [
      "purchase_order.created",
      "purchase_order.approved",
      "invoice.created",
      "vendor.created"
    ]
  }'

Example Response

json
{
  "data": {
    "id": 8,
    "name": "Procurement automation",
    "url": "https://integrations.example.com/waqti/webhook",
    "events": [
      "purchase_order.created",
      "purchase_order.approved",
      "invoice.created",
      "vendor.created"
    ],
    "secret": "k8mN2pQ9rS4tU7vW1xY5zA6bC0dE3fG8hI2jK5lM9nO1pQ4rS7tU0vW3xY6z",
    "is_active": true,
    "created_at": "2026-04-02T11:05:22Z"
  }
}

Get Webhook Details

http
GET /v1/webhooks/{id}

Returns one subscription including health fields. The signing secret is not included.

Example Request

bash
curl -X GET "https://api.waqti.sa/v1/webhooks/8" \
  -H "Authorization: Bearer 3|a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0" \
  -H "Accept: application/json"

Example Response

json
{
  "data": {
    "id": 8,
    "name": "Procurement automation",
    "url": "https://integrations.example.com/waqti/webhook",
    "events": [
      "purchase_order.created",
      "purchase_order.approved",
      "invoice.created",
      "vendor.created"
    ],
    "is_active": true,
    "failure_count": 0,
    "last_triggered_at": "2026-04-02T14:18:00Z",
    "last_failed_at": null,
    "last_failure_reason": null,
    "created_at": "2026-04-02T11:05:22Z",
    "updated_at": "2026-04-02T11:05:22Z"
  }
}

Update Webhook

http
PUT /v1/webhooks/{id}

Updates an existing subscription. All body fields are optional; omitted fields keep their current values.

Request Body

FieldTypeDescription
namestringDisplay name (max 100 characters)
urlstringDelivery URL (max 500 characters)
eventsarrayReplacement list of event types (min 1 if provided)
is_activebooleanEnable or pause deliveries

Example Request

bash
curl -X PUT "https://api.waqti.sa/v1/webhooks/8" \
  -H "Authorization: Bearer 3|a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      "purchase_order.approved",
      "purchase_order.rejected",
      "purchase_order.cancelled",
      "invoice.approved",
      "invoice.paid",
      "budget.threshold_reached"
    ],
    "is_active": true
  }'

Example Response

json
{
  "data": {
    "id": 8,
    "name": "Procurement automation",
    "url": "https://integrations.example.com/waqti/webhook",
    "events": [
      "purchase_order.approved",
      "purchase_order.rejected",
      "purchase_order.cancelled",
      "invoice.approved",
      "invoice.paid",
      "budget.threshold_reached"
    ],
    "is_active": true,
    "updated_at": "2026-04-02T15:40:10Z"
  }
}

Delete Webhook

http
DELETE /v1/webhooks/{id}

Permanently removes the subscription. No further deliveries are sent.

Example Request

bash
curl -X DELETE "https://api.waqti.sa/v1/webhooks/8" \
  -H "Authorization: Bearer 3|a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0" \
  -H "Accept: application/json"

Example Response

json
{
  "data": null
}

Send Test Event

http
POST /v1/webhooks/{id}/test

Queues an immediate test delivery to the configured URL using the same headers and signing rules as real events. The payload uses the synthetic event type webhook.test.

Example Request

bash
curl -X POST "https://api.waqti.sa/v1/webhooks/8/test" \
  -H "Authorization: Bearer 3|a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0" \
  -H "Accept: application/json"

Example Response (success)

json
{
  "data": {
    "status_code": 200,
    "response": "{\"received\":true}"
  }
}

If the endpoint does not return a success HTTP status, the API responds with 422 and an error message describing the failure.

Available events

Subscribe using the API event strings in POST / PUT requests. The shorthand column matches common naming (po.*, etc.); only the API value is accepted by the server.

API eventShorthandDescription
purchase_order.createdpo.createdA new purchase order was created
purchase_order.approvedpo.approvedA purchase order was approved
purchase_order.rejectedpo.rejectedA purchase order was rejected
purchase_order.cancelledpo.cancelledA purchase order was cancelled
invoice.createdinvoice.createdA new invoice was created
invoice.approvedinvoice.approvedAn invoice was approved
invoice.paidinvoice.paidAn invoice was marked as paid
vendor.createdvendor.createdA new vendor was created
vendor.updatedvendor.updatedVendor details were updated
budget.threshold_reachedbudget.threshold_reachedA budget crossed its alert threshold

Additional event types may be available in the product over time; your integration should ignore unknown event values it does not handle.

Webhook payload and signature verification

Each delivery is an HTTP POST with Content-Type: application/json. The JSON body has this shape:

Example delivery body

json
{
  "id": "9f2d5c1e-6b4a-4e8d-9c3f-1a7e2d5b8c60",
  "event": "purchase_order.approved",
  "created_at": "2026-04-02T16:22:11+00:00",
  "tenant_id": "acme-corp",
  "data": {
    "purchase_order_id": 442,
    "po_number": "PO-2026-0442",
    "status": "approved",
    "total_amount": 128750.5,
    "currency": "SAR",
    "approved_at": "2026-04-02T16:22:00+00:00"
  }
}

Headers

HeaderDescription
X-Waqti-EventSame string as the event field in the body
X-Waqti-SignatureHex-encoded HMAC-SHA256 of the raw request body using your subscription secret
X-Waqti-DeliveryInternal delivery id (useful for support and idempotency logging)
User-AgentWaqti-Webhook/1.0

Verifying HMAC SHA-256

  1. Read the raw HTTP body as a string (before parsing JSON).
  2. Compute HMAC_SHA256(raw_body, secret) and encode as lowercase hexadecimal.
  3. Compare the result to X-Waqti-Signature using a constant-time comparison (e.g. hash_equals in PHP).

If the signature does not match, reject the request and do not trust the JSON.

WARNING

Whitespace, key order, and Unicode normalization affect the raw body. Verify against the bytes Waqti actually sent, not a re-serialized copy of parsed JSON, unless your framework guarantees identical output.

Retry policy

Failed deliveries (non-2xx HTTP status, timeouts, or network errors) are retried automatically. Expect up to about three retries after the initial attempt, with exponential backoff between each try. After the maximum attempts are exhausted, the delivery is marked failed.

If failures persist, Waqti may increment a failure counter on the subscription and automatically disable the webhook after many consecutive failures—fix the endpoint and re-enable the subscription via PUT or the admin UI.

Security best practices

  • Use HTTPS only for webhook URLs so payloads and metadata are encrypted in transit.
  • Verify every request with X-Waqti-Signature before acting on the payload; treat missing or invalid signatures as attacks or misconfiguration.
  • Store the secret from the create response in a secrets manager or environment variable, not in source control.
  • Respond quickly with 2xx after validating and enqueueing work; perform heavy processing asynchronously so Waqti does not time out and retry unnecessarily.
  • Design for duplicates: at-least-once delivery is possible; use id (payload UUID) or your own idempotency keys to deduplicate.

Built by M & L Technologies