1. Webhooks
AutopayEG
  • Invoices
    • Create invoice
      POST
    • Get invoice
      GET
    • Cancel invoice
      POST
  • Merchant
    • Current merchant
      GET
  • Webhooks
    • Receive Autopay webhook
      POST
  • Schemas
    • InvoiceStatus
    • Provider
    • ErrorEnvelope
    • CreateInvoiceRequest
    • Invoice
    • CreateInvoiceResponse
    • GetInvoiceResponse
    • MerchantMeResponse
    • MerchantFees
    • MerchantTransaction
    • WebhookCustomer
    • WebhookEnvelope
    • WebhookData
  1. Webhooks

Receive Autopay webhook

POST
/webhooks/autopay
This is your endpoint. Autopay POSTs here using the webhook_url you set
on the invoice. Change the server URL in your docs/client to your real host.
Return HTTP 2xx within 10 seconds to acknowledge. Autopay retries a failed
delivery a few times (about 30 seconds, then 2 minutes) with the same
X-Autopay-Delivery-Id and the same JSON body. Your handler must be
idempotent.

Handler order (do this in this order)#

1.
Read the raw request body as bytes/string. Do not parse-then-re-serialize.
2.
Verify HMAC (below). If it fails, return 401 and do not change the order.
3.
If you have already processed this X-Autopay-Delivery-Id, return 200 and stop.
4.
Switch on event and update your order (table below).
5.
Return 200. Do slow work (email, ERP) after the response, in a queue.

HMAC (required)#

Secret: dashboard webhook secret — not X-API-Secret.
message = "{X-Autopay-Timestamp}.{X-Autopay-Delivery-Id}.{raw_body}"
X-Autopay-Signature = HMAC-SHA256(webhook_secret, message)  // lowercase hex
Autopay encodes the body with JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE.
If you decode JSON and encode it again, the signature will not match.
PHP:
Node.js:
During webhook-secret rotation (~24 hours), try the current secret then the
previous one. Outbound deliveries are always signed with the current secret.
Envelope timestamp is ISO-8601. Header X-Autopay-Timestamp is unix seconds
as a string. Do not HMAC the JSON timestamp field.

What to do per event#

eventFulfill order?Your action
invoice.completedYesMark paid using invoice_id / external_reference. Read data.fees only as customer_total and merchant_net.
invoice.under_reviewNoHold the order. A later completed or failed will follow after review.
invoice.failedNoPayment was rejected. Release hold.
invoice.cancelledNoInvoice cancelled. Release hold.
invoice.expiredNoTimed out. Release hold.
There is no invoice.paid. Unknown event names: return 200 and ignore
(do not 4xx, or Autopay will retry).
data.provider is vodafone_cash or instapay when a transaction exists.

Confirm your webhook works#

A test payment produces a POST with the three X-Autopay-* headers.
Tampering with the body (or using the API secret instead of the webhook secret)
fails verification.
Repeating the same X-Autopay-Delivery-Id does not double-charge / double-ship.
localhost / 127.0.0.1 webhook URLs are rejected at invoice create time.
Use a public HTTPS URL (or a tunnel) in production.

Request

Authorization
API Key
Add parameter in header
X-API-Key
Example:
X-API-Key: ********************
API Key
Add parameter in header
X-API-Secret
Example:
X-API-Secret: ********************
or
Bearer Token
Provide your bearer token in the
Authorization
header when making requests to protected resources.
Example:
Authorization: Bearer ********************
or
Header Params

Body Params application/jsonRequired

Examples

Responses

🟢200
application/json
Acknowledge receipt. Any 2xx is enough; Autopay ignores the body.
Return 200 also when this delivery id was already processed.
Bodyapplication/json

🟠401
Request Request Example
Shell
JavaScript
Java
Swift
cURL
curl --location '/webhooks/autopay' \
--header 'X-Autopay-Signature;' \
--header 'X-Autopay-Timestamp;' \
--header 'X-Autopay-Delivery-Id;' \
--header 'X-API-Key: <api-key>' \
--header 'X-API-Secret: <api-key>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "event": "invoice.completed",
    "invoice_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "external_reference": "ORD-1001",
    "timestamp": "2026-08-17T10:12:00+00:00",
    "data": {
        "invoice_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
        "external_reference": "ORD-1001",
        "amount": "100.00",
        "currency": "EGP",
        "status": "completed",
        "transaction_id": 42,
        "provider": "instapay",
        "paid_at": "2026-08-17T10:12:00+00:00",
        "customer": {
            "name": "Ahmed Ali",
            "email": "customer@example.com",
            "phone": "01012345678",
            "metadata": {
                "order_id": "1001"
            }
        },
        "transaction": {
            "id": 42,
            "uuid": "a3f1c8e2-4b5d-4e6f-8a9b-1c2d3e4f5a6b",
            "invoice_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
            "provider": "instapay",
            "status": "verified",
            "currency": "EGP",
            "amount": "100.00",
            "received_amount": "100.00",
            "sender_identifier": "ahmed@instapay",
            "sender_name": "Ahmed Ali",
            "reference": "123456789",
            "is_flagged": false,
            "flag_reason": null,
            "fees": {
                "customer_total": "103.00",
                "merchant_net": "100.00"
            },
            "matched_at": "2026-08-17T10:12:00+00:00",
            "created_at": "2026-08-17T10:05:00+00:00"
        }
    }
}'
Response Response Example
200 - Ack
{
    "received": true
}
Modified at 2026-08-17 11:17:23
Previous
Current merchant
Next
InvoiceStatus
Built with