payment.succeeded
Sent when a payment completes successfully. This is the event most integrations care about — fulfil the order when you receive it (after verifying the signature).
How webhooks are delivered
SwiftPayIQ sends an HTTP POST to the endpoint URL you configured in the dashboard, with a JSON body of the shape:
{ "event": "payment.succeeded", "data": { /* … */ } }
Headers on every delivery:
| Header | Value |
|---|---|
| Content-Type | application/json |
| X-SwiftPay-Signature | t=<unix-seconds>,v1=<hex-hmac> |
Your endpoint should respond with any 2xx status as quickly as possible. A non-2xx response (or a timeout — the request is aborted after 10 seconds) is treated as a failure and retried.
Verifying the signature
Every delivery is signed with the signing secret of your webhook (shown when you create or rotate it in the dashboard; it starts with whsec_). Verify it before trusting the payload:
- Read the X-SwiftPay-Signature header and parse the t (timestamp) and v1 (signature) values.
- Reject the request if t is more than 5 minutes away from your current time (replay protection).
- Build the signed string: "{t}.{rawRequestBody}" — use the raw, unparsed request body bytes exactly as received.
- Compute HMAC-SHA256(signingSecret, signedString) and hex-encode it.
- Compare it to v1 using a constant-time comparison. If they match, the request is authentic.
// Node.js (Express) example
const crypto = require('crypto');
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map(p => p.split('=')));
const t = parts.t, v1 = parts.v1;
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // 5 min
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(v1, 'hex'));
}
Capture the raw request body for signature verification — re-serializing parsed JSON will change the bytes and the signature will not match.
Retries
If your endpoint doesn't return 2xx, delivery is retried up to 6 attempts total (5 retries) with increasing back-off: 1 min → 5 min → 30 min → 2 hours → 8 hours (about 10.5 hours end-to-end). A 4xx response other than 408/429 is treated as a permanent failure and is not retried. Make your handler idempotent — the same event may arrive more than once. You can inspect delivery history and re-send from the dashboard.
Payload
Example payload
{
"data": {
"amount": "50000",
"provider": "ALQASEH"
}
}Response
Return any 2xx to acknowledge receipt.
Changes
Webhooks have no change history: a webhook is keyed by its event name rather than a path and method, so no recorded change can name it.