Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/own-pay/OwnPay-Documentation/llms.txt

Use this file to discover all available pages before exploring further.

Webhooks are how OwnPay notifies your application of payment events in real time. Rather than polling the API on an interval to check whether a payment has changed status, you register a URL and OwnPay delivers a signed HTTP POST to that URL as soon as the event occurs. This event-driven architecture reduces latency, eliminates unnecessary API calls, and enables your application to react to payment completions, failures, and refunds the moment they happen.

How OwnPay Delivers Webhooks

When a payment event occurs — for example, a gateway confirms a successful payment — OwnPay:
  1. Constructs a JSON payload describing the event
  2. Signs the payload with HMAC-SHA256 using the webhook secret for that brand
  3. Places the signed request in the delivery queue
  4. Dispatches an HTTP POST to your registered callback_url within the same request cycle (or immediately after, via the background queue)
Your endpoint must respond with an HTTP 2xx status code within 30 seconds. If it does not, OwnPay considers the delivery failed and schedules a retry.
Do not perform slow operations (database writes, email sending, external API calls) inline in your webhook handler. Respond with 200 OK immediately after signature verification, then process the event asynchronously in a background job or queue worker. Slow handlers that miss the 30-second window will trigger unnecessary retries.

Automatic Retry Schedule

If your endpoint fails to respond with 2xx within 30 seconds, or returns a 4xx / 5xx status, OwnPay retries delivery on the following schedule:
AttemptDelay After Previous Failure
Retry 130 seconds
Retry 25 minutes
Retry 330 minutes
Retry 42 hours
Retry 58 hours
Retry 624 hours
After 6 failed retries, the delivery is marked as permanently failed. You can manually re-trigger delivery from the webhook delivery logs. Transient server issues, deployments, or network blips during the initial delivery are covered by this retry window without any data loss.

HMAC-SHA256 Signature

Every webhook request includes an X-OwnPay-Signature header containing an HMAC-SHA256 signature of the request body, keyed with your brand’s webhook secret. Verify this signature on every incoming webhook request before processing the payload — this prevents replay attacks and ensures the request genuinely originated from your OwnPay installation.

Signature Verification

The signature is computed as:
HMAC-SHA256(secret, "{timestamp}.{raw_request_body}")
Where {timestamp} is the Unix timestamp from the X-OwnPay-Timestamp header and {raw_request_body} is the unparsed JSON string of the request body.
$signature  = $_SERVER['HTTP_X_OWNPAY_SIGNATURE'] ?? '';
$timestamp  = $_SERVER['HTTP_X_OWNPAY_TIMESTAMP'] ?? '';
$rawBody    = file_get_contents('php://input');
$secret     = getenv('OWNPAY_WEBHOOK_SECRET');

$expected = hash_hmac('sha256', "{$timestamp}.{$rawBody}", $secret);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('Invalid signature');
}

// Signature verified — safe to process
$event = json_decode($rawBody, true);
http_response_code(200);
// Queue async processing here
Always use a constant-time comparison function (hash_equals in PHP, crypto.timingSafeEqual in Node.js, hmac.compare_digest in Python) when comparing signatures. Standard string equality checks are vulnerable to timing attacks.

The payment.completed Event Payload

The payment.completed event is the most commonly handled webhook event. It fires when a gateway has confirmed successful authorization and OwnPay has recorded the transaction in the ledger.
{
  "type": "payment.completed",
  "id": "evt_z9y8x7w6v5u4t3s2",
  "created_at": "2024-06-15T14:22:31Z",
  "data": {
    "id": "pay_a1b2c3d4e5f6",
    "amount": 4999,
    "currency": "USD",
    "status": "completed",
    "description": "Order #1042 — 1x Premium Plan",
    "customer_email": "jane@example.com",
    "customer_name": "Jane Smith",
    "gateway": "stripe",
    "gateway_transaction_id": "ch_3NkL4v2eZvKYlo2C1abc",
    "brand_id": "brand_xyz123",
    "test": false,
    "metadata": {
      "order_id": "1042",
      "plan": "premium"
    },
    "paid_at": "2024-06-15T14:22:29Z",
    "created_at": "2024-06-15T14:20:11Z"
  }
}
FieldTypeDescription
typestringEvent type identifier (e.g. payment.completed)
idstringUnique event ID — use this for idempotent processing
created_atISO 8601When the event was generated
data.idstringThe OwnPay payment ID
data.amountintegerAmount in the smallest currency unit (cents, paisa, etc.)
data.currencystringISO 4217 currency code
data.statusstringPayment status at time of event
data.gatewaystringGateway slug that processed the payment
data.gateway_transaction_idstringThe processor’s own transaction reference
data.testbooleantrue if the gateway was in test mode
data.metadataobjectCustom key-value pairs from the original payment intent
data.paid_atISO 8601Timestamp of gateway authorization

All Event Types

EventFired When
payment.createdA new payment intent is created
payment.completedGateway confirms successful authorization
payment.failedGateway declines the payment
payment.refundedA completed payment is reversed by a refund
payment.cancelledPayment is explicitly cancelled before completion
payment.disputedA chargeback is initiated by the customer’s bank
EventFired When
customer.createdA new customer record is created
customer.updatedA customer’s details are modified
customer.deletedA customer record is removed
EventFired When
gateway.addedA new gateway is configured on a brand
gateway.updatedA gateway’s settings are modified
gateway.removedA gateway is deleted from a brand

Configuring Webhooks

Webhooks are configured at the brand level. Navigate to Developers → Webhooks within a brand context and register your endpoint URL.
1

Add a Webhook Endpoint

Go to Developers → Webhooks and click Add Endpoint. Enter your HTTPS URL — webhook endpoints must be publicly accessible and must use HTTPS. Plain HTTP endpoints are not accepted.
2

Copy the Webhook Secret

After saving, OwnPay displays the webhook secret for this endpoint. Copy it immediately and store it as an environment variable in your application (OWNPAY_WEBHOOK_SECRET). It is not shown again after you navigate away.
3

Select Events to Subscribe To

By default, all events are delivered to your endpoint. You can filter to specific event types to reduce noise in your handler.
4

Test the Endpoint

Click Send Test Event (or use POST /webhooks/tests via the API) to dispatch a sample payment.completed payload to your endpoint. Check your server logs to confirm the signature verification passes and the payload is received correctly.

Delivery Logs

OwnPay maintains a full delivery log for every webhook dispatch. To view it:
  • Admin panel: Go to Developers → Webhooks → Delivery Log
  • API: GET /webhooks/deliveries
Each log entry shows the event type, the HTTP status code returned by your server, the response body, the delivery timestamp, and whether retries are pending. Failed deliveries that have not yet exhausted all retries show as pending_retry. To manually re-trigger a failed delivery:
  • Admin panel: Click Retry on the failed delivery row
  • API: POST /webhooks/tests with the original event ID

Testing Webhooks Locally

During development your webhook endpoint is typically running on localhost, which is not reachable from the internet. Use a tunneling tool to expose it temporarily:
# Using ngrok
ngrok http 3000

# Your local endpoint is now reachable at:
# https://abc123.ngrok-free.app/webhooks/ownpay
Register the ngrok URL in OwnPay’s webhook settings, make a test payment in the sandbox, and inspect the request in ngrok’s inspector dashboard at http://localhost:4040.
Always test your webhook handler against the full retry schedule before going live. Send a test event, intentionally return a non-2xx status from your endpoint, and confirm that OwnPay retries on the expected schedule and that your idempotency logic prevents duplicate processing when the retried delivery succeeds.

Build docs developers (and LLMs) love