Guide to Ryft Webhooks: Features and Best Practices
Ryft is a payments platform for marketplaces and software platforms in the UK and Europe. Alongside card payments it handles sub-accounts for the businesses on your platform, payouts to them, subscriptions and FX conversions. Webhooks keep your platform in step with all of that: a captured payment, a sub-account update, a dispute, or a conversion settling.
Read on for Ryft's one-endpoint model, the event categories and envelope, verifying the Signature header, and the tight 1.5-second response window.
What are Ryft webhooks?
Each Ryft account has a single webhook endpoint. If you need more, Ryft asks you to contact support. You manage it in the Ryft Portal or through the Webhook API, which can create, update, retrieve and delete endpoints, and you choose which events it receives.
Ryft groups its events into four categories: Sub-Accounts, Payments, Subscriptions and Conversions. Payment events cover in-person as well as online payments.
Ryft webhook features
| Feature | Details |
|---|---|
| Signature | hex HMAC-SHA256 |
| Signature header | signature |
| Body | JSON |
| Event routing | Body field eventType |
From Ryft's webhooks guide:
- One endpoint per account, managed in the Portal or with the Webhook API.
- The Portal's Events section lists every event sent to your endpoint.
- Ryft expects a
200within 1.5 seconds. Anything else is a failed attempt. - Failed deliveries are retried after 0, 1, 5, 10, 10 and 10 minutes, so six attempts over about 36 minutes.
- Ryft recommends keeping handlers lightweight and processing asynchronously.
Events and payloads
Route on eventType. The sub-account events come first in Ryft's catalogue:
| Event routing | Body field eventType |
The other categories:
- Payouts:
Payout.created,Payout.status_updated. - Payments:
PaymentSession.captured,.capture_failed,.approved,.declined,.voided,.void_failed,.refundedand.refund_failed;PlatformFee.createdand.refunded;Transfer.createdand.updated;Customer.created,.updatedand.deleted;Dispute.created,.challengedand.closed. - Subscriptions:
Subscription.created,.updated,.paused,.resumed,.cancelledand.ended. Ryft notes that pausing happens asynchronously. - Conversions:
Conversion.createdandConversion.updated, for an FX conversion being priced and settling.
Every body has the same envelope: id (prefixed ev_), eventType, data with the resource, accountId, createdTimestamp, lastUpdatedTimestamp and deliveryStatus. Timestamps appear as Unix seconds in the examples. This Conversion.created example is from Ryft's docs, with IDs masked:
{
"id": "ev_XXXXXXXX",
"eventType": "Conversion.created",
"data": {
"id": "cv_XXXXXXXX",
"sell": {
"amount": 100000,
"currency": "EUR"
},
"buy": {
"currency": "GBP"
},
"status": "InProgress",
"reason": "Monthly EUR settlement",
"createdTimestamp": 1750000000
},
"accountId": "ac_XXXXXXXX",
"createdTimestamp": 1750000000,
"lastUpdatedTimestamp": 1750000000,
"deliveryStatus": "Success"
}
In payment events, data.paymentMethod only appears when the session used a saved payment method.
Setting up Ryft webhooks
- In the Ryft Portal, click your profile icon, choose Developer, then Webhooks in the sidebar.
- Create the endpoint with your HTTPS URL, for example
https://example.com/webhooks/ryft, and select the events. - Copy the webhook secret and store it as
RYFT_WEBHOOK_SECRET. - Trigger a sandbox payment and check the Portal's Events list.
Since you only get one endpoint, Hookdeck is a practical way to send events to several services. Create a Ryft source with the secret, register its URL as your Ryft endpoint, and add a connection per service. For local testing:
npx hookdeck-cli listen 3000 ryft --path /webhooks/ryft
Securing Ryft webhooks
Ryft's guide says to HMAC-SHA256 the raw body with the webhook secret and compare it with the Signature header. It doesn't name an encoding. Ryft's .NET SDK settles it: the secret is used as ASCII bytes, the body as UTF-8, and the digest is lowercase hex. The header holds only the digest, with no timestamp.
Node.js verification
const crypto = require('node:crypto');
function verifySignature(rawBody, headers, secret) {
if (!Buffer.isBuffer(rawBody) || typeof secret !== 'string' || !secret) return false;
const signature = headers['signature'];
if (typeof signature !== 'string') return false;
const key = Buffer.from(secret, 'utf8');
const message = rawBody;
const candidates = [signature];
const expected = crypto.createHmac('sha256', key).update(message).digest();
return candidates.some((candidate) => {
if (!/^[0-9a-fA-F]{64}$/.test(candidate)) return false;
const received = Buffer.from(candidate, 'hex');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
});
}
The handler verifies, queues by event id and answers immediately. With 1.5 seconds available, even a slow database write can tip a delivery into a retry.
const express = require('express');
const app = express();
app.post('/webhooks/ryft', express.raw({ type: '*/*' }), async (req, res) => {
const secret = process.env.RYFT_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500);
if (!verifySignature(req.body, req.headers, secret)) return res.sendStatus(401);
let parsed;
try {
parsed = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return res.sendStatus(400);
const { id, eventType, accountId, data } = parsed;
try {
await queue.add('ryft', { id, eventType, accountId, data }, { jobId: id });
} catch {
return res.sendStatus(503);
}
res.sendStatus(200);
});
Using id as the job ID means a retried event that's already queued is ignored. Keep the queue close to the endpoint so the write itself stays well under the limit.
Hookdeck's Ryft source type runs the same check. It responds 200 to Ryft straight away and records any mismatch as failed verification. Ryft signs no timestamp, and Hookdeck adds no time window.
Make Ryft webhooks production-ready. Hookdeck Event Gateway answers Ryft well inside 1.5 seconds and fans your single Ryft endpoint out to as many services as you need.
Ryft webhook limitations and pain points
A 1.5-second deadline
The Problem: Ordinary handlers time out and trigger retries.
Why It Happens: Ryft counts any delivery without a 200 in 1.5 seconds as failed. Cold starts, TLS setup and a database write can use that up.
Workarounds:
- Verify and enqueue only, then return
200. - Keep the endpoint warm and close to a fast queue.
How Hookdeck Can Help: Hookdeck replies to Ryft immediately, then delivers to your service with a longer timeout.
Retries stop after about 36 minutes
The Problem: An outage longer than half an hour loses events.
Why It Happens: Ryft makes six attempts, the last 36 minutes after the first.
Workarounds:
- Watch the Portal's Events list after any incident.
- Reconcile payments, payouts and sub-accounts from the Ryft API.
How Hookdeck Can Help: Hookdeck holds the event and keeps retrying your service on a schedule you configure, with replay for anything that still failed.
One endpoint per account
The Problem: Accounting, fulfilment and onboarding services can't each have their own endpoint.
Why It Happens: Ryft currently allows one webhook endpoint per account unless support adds more.
Workarounds:
- Build an internal router that receives Ryft events and forwards them.
- Ask Ryft support for additional endpoints.
How Hookdeck Can Help: Hookdeck connections route one Ryft source to several destinations, with filters on eventType so each service gets only what it needs.
Encoding isn't in the guide
The Problem: Implementations that guess base64 fail every check.
Why It Happens: The webhooks guide only says to HMAC-SHA256 the raw body. Hex comes from Ryft's .NET SDK.
Workarounds:
- Use lowercase hex, as the SDK does, and compare in constant time.
- Use one of Ryft's SDKs if one exists for your language.
How Hookdeck Can Help: Hookdeck's Ryft source already uses hex, so you can confirm verification works before writing your own.
Best practices
Deduplicate on the event id
Retries reuse the ev_ id. Record it before acting, and see our idempotency guide for patterns.
Process payments asynchronously
Ryft itself recommends lightweight handlers. Queue the event and do the work afterwards. Our guide to asynchronous webhook processing covers why.
Use accountId for sub-accounts
On a platform, accountId tells you which sub-account an event belongs to. Route and store by it.
Order by lastUpdatedTimestamp
Retries mean an older state can arrive after a newer one. Compare lastUpdatedTimestamp with what you've stored before overwriting a payment or payout.
Conclusion
Ryft sends payment, sub-account, subscription and conversion events to one endpoint per account, signed with a lowercase hex HMAC-SHA256 in Signature. The constraints are the 1.5-second deadline and a retry schedule that ends after about 36 minutes, so acknowledge fast and reconcile after outages.
Hookdeck Event Gateway meets Ryft's deadline, extends its retries and routes the single endpoint to all your services.
Get started with Hookdeck for free and handle Ryft webhooks reliably in minutes.