Webhooks
Webhooks are how Ridley tells your backend that something happened. Treat them as the source of truth. The browser redirect is for user experience only.
Configure the endpoint per Source in the dashboard (Settings → Sources →
Webhook URL). Each Source has its own whsec_ signing secret.
Events
Section titled “Events”| Event | Fires when |
|---|---|
payment.completed |
Payment captured successfully |
payment.authorized |
Funds held (manual capture) |
payment.captured |
A held payment was captured |
payment.failed |
Authorization or capture failed |
payment.refunded |
Payment refunded (full or partial) |
payment.voided |
A hold was released |
recurring.charged |
Subscription charged successfully |
recurring.failed |
Subscription charge failed |
recurring.suspended |
Subscription suspended after max retries |
recurring.cancelled |
Subscription cancelled |
recurring.paused / recurring.resumed |
Subscription paused / resumed |
recurring.updated |
Subscription changed |
recurring.payment_method_updated |
Card on file replaced |
invoice.created / invoice.sent / invoice.paid / invoice.overdue |
Invoice lifecycle |
Request
Section titled “Request”POST /your/webhook/urlContent-Type: application/jsonX-Webhook-Signature: t=1735689600,v1=5257a8...X-Webhook-Event: payment.completedX-Webhook-Event-Id: 550e8400-e29b-41d4-a716-446655440000
{ "event": "payment.completed", "data": { "payment_id": 42, "reference": "order-12345", "amount": 150.0, "currency": "JMD", "status": "PAID", "card_last": "4242", "card_brand": "visa", "metadata": {} }}Verifying the signature
Section titled “Verifying the signature”The signature is an HMAC-SHA256 of "{timestamp}.{raw_request_body}" using your
Source’s whsec_ secret. Compute it over the raw body and compare in
constant time.
$parts = collect(explode(',', $request->header('X-Webhook-Signature'))) ->mapWithKeys(function ($part) { [$k, $v] = explode('=', $part, 2); return [$k => $v]; });
$expected = hash_hmac( 'sha256', $parts['t'].'.'.$request->getContent(), $source->webhook_secret,);
if (! hash_equals($expected, $parts['v1'])) { abort(400, 'Invalid signature');}Delivery & retries
Section titled “Delivery & retries”- Respond with a 2xx quickly and do heavy work asynchronously.
- Non-2xx responses are retried 3 times with increasing backoff.
- Every attempt is logged (status, response body, timestamp) for debugging.
- Endpoints must be public; private/reserved addresses are blocked.
Events are also recorded
Section titled “Events are also recorded”Each delivery is stored against the payment so you can inspect failures in the
dashboard. Use the recorded event_id to correlate with your own logs.