Webhooks
Webhooks notify your server when events occur in your Fyber account. Use them to trigger actions like fulfilling orders, sending emails, or updating your database.
Setup
- Go to Fyber Console > Developer > Webhooks
- Add your endpoint URL (e.g.,
https://yoursite.com/webhooks/fyber) - Select the events you want to receive
- Copy your webhook secret (
whsec_...)
Verify Signatures
Always verify webhook signatures to ensure requests come from Fyber.
JavaScript
import express from 'express';
import { Fyber } from '@fyber.one/sdk-js';
const app = express();
app.post('/webhooks/fyber', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['fyber-signature'];
const webhookSecret = 'whsec_your_webhook_secret';
let event;
try {
event = Fyber.webhooks.verify(
req.body.toString(),
signature,
webhookSecret
);
} catch (err) {
console.error('Webhook verification failed:', err.message);
return res.status(400).send('Invalid signature');
}
// Handle the event — the payload fields live in data.attributes
switch (event.type) {
case 'checkout.session.completed':
handleCheckoutComplete(event.data.attributes);
break;
case 'payment.succeeded':
handlePaymentSucceeded(event.data.attributes);
break;
// ... handle other events
}
res.json({ received: true });
});PHP
<?php
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_FYBER_SIGNATURE'];
$webhookSecret = 'whsec_your_webhook_secret';
try {
$event = Fyber::verifyWebhook($payload, $signature, $webhookSecret);
switch ($event['type']) {
case 'checkout.session.completed':
handleCheckoutComplete($event['data']['object']);
break;
case 'payment.succeeded':
handlePaymentSucceeded($event['data']['object']);
break;
}
http_response_code(200);
echo json_encode(['received' => true]);
} catch (Fyber\Exceptions\FyberException $e) {
http_response_code(400);
echo 'Invalid signature';
}C#
[HttpPost("webhook")]
public IActionResult HandleWebhook()
{
var payload = new StreamReader(Request.Body).ReadToEnd();
var signature = Request.Headers["fyber-signature"].ToString();
var webhookSecret = "whsec_your_webhook_secret";
try
{
var webhookEvent = FyberClient.VerifyWebhook(payload, signature, webhookSecret);
switch (webhookEvent.Type)
{
case "checkout.session.completed":
HandleCheckoutComplete(webhookEvent.Data);
break;
case "payment.succeeded":
HandlePaymentSucceeded(webhookEvent.Data);
break;
}
return Ok();
}
catch (FyberException)
{
return BadRequest("Invalid signature");
}
}Flutter
try {
final event = Fyber.verifyWebhook(
payload: rawBody,
signature: request.headers['fyber-signature']!,
secret: 'whsec_your_webhook_secret',
);
switch (event['type']) {
case 'payment.succeeded':
handlePaymentSucceeded(event['data']['object']);
break;
}
} on FyberException catch (e) {
print('Invalid webhook: ${e.message}');
}Event Types
Checkout Events
| Event | Description |
|---|---|
checkout.session.completed | Customer completed checkout |
checkout.session.expired | Session expired without payment |
Payment Events
| Event | Description |
|---|---|
payment.succeeded | Payment completed successfully |
payment.failed | Payment was declined |
payment.canceled | Payment was canceled/voided |
Refund Events
| Event | Description |
|---|---|
refund.created | Refund initiated |
refund.succeeded | Refund completed |
refund.failed | Refund failed |
Token Events
| Event | Description |
|---|---|
token.created | Card saved successfully |
token.deleted | Saved card removed |
Subscription Events
| Event | Description |
|---|---|
subscription.created | Subscription started |
subscription.payment_succeeded | Recurring payment successful |
subscription.payment_failed | Recurring payment failed |
subscription.trial_ending | Trial ends in 3 days |
subscription.canceled | Subscription ended |
Installment Events
| Event | Description |
|---|---|
installment.plan_created | Plan activated |
installment.payment_succeeded | Scheduled payment completed |
installment.payment_failed | Scheduled payment failed |
installment.plan_completed | All payments completed |
installment.plan_defaulted | Customer defaulted |
Event Object
{
"id": "3f8a1b2c-6d4e-4f2a-9b1c-2e7d8a9f0b1c",
"type": "payment.succeeded",
"created": "2024-01-15T10:30:00.1234567Z",
"data": {
"object": "payment",
"id": "7c9e6b1d-2a3f-4b5c-8d9e-0f1a2b3c4d5e",
"attributes": {
"paymentId": "7c9e6b1d-2a3f-4b5c-8d9e-0f1a2b3c4d5e",
"amount": 5000,
"currency": "JMD",
"customerEmail": "customer@example.com",
"paymentMethod": "card",
"cardBrand": "visa",
"metadata": { "orderId": "1234" }
}
}
}Notes:
- IDs are UUIDs (no
evt_/pay_prefixes). - The event payload lives in
data.attributes(camelCase keys);data.objectis the entity type string anddata.idis the entity's ID. createdis the event creation time;amountvalues are in the smallest currency unit (e.g. cents).
Signature Header
Each delivery carries the signature in the Fyber-Signature header (also duplicated as X-Fyber-Signature):
Fyber-Signature: t=1705314600,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdtis the unix timestamp at dispatch time (fresh on every retry).v1is lowercase-hexHMAC-SHA256("{t}.{raw_body}", secret)wheresecretis your endpoint's fullwhsec_...signing secret.- Verify against the raw request body bytes — never a re-serialized copy.
Best Practices
Respond Quickly
Return a 2xx response immediately, then process the event asynchronously:
app.post('/webhooks/fyber', async (req, res) => {
// Verify signature first
const event = Fyber.webhooks.verify(...);
// Respond immediately
res.json({ received: true });
// Process asynchronously
await processEventAsync(event);
});Handle Duplicates
Events may be delivered more than once. Use the event ID to deduplicate:
const processedEvents = new Set();
function handleEvent(event) {
if (processedEvents.has(event.id)) {
return; // Already processed
}
processedEvents.add(event.id);
// Process event...
}Retry Failed Deliveries
If your endpoint returns a non-2xx response (or takes longer than 10 seconds), Fyber retries with these delays:
- 1st retry: 1 minute
- 2nd retry: 5 minutes
- 3rd retry: 15 minutes
- 4th retry: 1 hour
- 5th retry: 2 hours
After 6 total attempts the delivery is marked as failed. An endpoint that fails 10 consecutive deliveries is automatically disabled.
Test Webhooks Locally
Use a tool like ngrok to expose your local server:
ngrok http 3000
# Use the ngrok URL as your webhook endpointWebhook Logs
View webhook delivery history in the Fyber Console under Developer > Webhooks > Logs.