Payment Button - Hook
Custom payment buttons offer the ability to send notifications when a successful payment is made, this is achieved by configuring a hook.
Activate a Hook for a Payment Button
Activating a Hook takes only 2 steps:
Define the default API key that will be used for signing each Hook request and will secure the information been shared
Setup a URL to the one we will send out the notifications.
To use the Hook you must first create a Dynamic Payment Button: View tutorial
1. Define the API Key credentials for Hooks
To define the credentials head over to Settings > API Credentials

In the example above we can see a dropdown list of the options from where we will choose the credential that we will use as default for hooks.
2. Setup your systems URL
The second step is to setup the path to which requests will be made each time successful payments are received on a custom payment button. Some considerations to keep in mind:
It must be a public path (accessible from the Internet).
You must have a valid security certificate (path must use HTTPS).
To configure the URL, simply go into the settings of the payment button(s) in which you want to activate the Hook, click on edit and then on "Advanced Options".
Once in the Advanced Options you will find a field called "Hook URL" where you can place the route.

Signature Validation
1. Official helper libraries
Prefer using our maintained helpers:
Runtime | Package | Docs & Install |
---|---|---|
Python ≥ 3.8 | fygaro-webhook | `pypi.org/project/fygaro-webhook` |
Node ≥ 16 | @fygaro/webhook | `npmjs.com/package/@fygaro/webhook` |
TL;DR: Install the package, create a validator with your shared secret(s), call verify_signature, and process the JSON only if it returns True (Python) or true (JS).
2. DIY algorithm (for every other language)
If a helper isn't available, implement these steps:
Read the raw request body (bytes).
Parse the Fygaro-Signature header → extract timestamp t= and every v1= hash.
Parse Fygaro-Key-ID to know which secret to use.
(Recommended) reject if abs(now - t) > 300 s (basic replay protection).
Re-create the message exactly as Fygaro did:
message = t + "." + raw_body
Compute HMAC-SHA-256(secret, message) → hex.
Constant-time compare the result against each `v1`; accept if any match.
On success, respond HTTP 200 and continue processing the JSON payload; otherwise return 4xx.
3. Language-specific examples
PHP ≥ 7.4 (framework-agnostic)
<?php
// 1. Map Fygaro-Key-ID → shared secret (raw string)
$secrets = [
'1234abcd' => 'your-secret-here',
];
// 2. Read raw body & headers
$rawBody = file_get_contents('php://input') ?: '';
$sigHdr = $_SERVER['HTTP_FYGARO_SIGNATURE'] ?? '';
$keyId = $_SERVER['HTTP_FYGARO_KEY_ID'] ?? '';
if ($sigHdr === '' || $keyId === '') {
http_response_code(400);
exit('Missing Fygaro headers');
}
// 3. Parse Fygaro-Signature (t=...,v1=...,v1=...)
$timestamp = null;
$hashes = [];
foreach (explode(',', $sigHdr) as $part) {
[$k, $v] = array_map('trim', explode('=', $part, 2));
if ($k === 't') { $timestamp = $v; }
if ($k === 'v1') { $hashes[] = $v; }
}
if (!$timestamp || !$hashes || !isset($secrets[$keyId])) {
http_response_code(400);
exit('Malformed signature header');
}
// 4. Optional replay check (±5 min window)
if (abs(time() - (int)$timestamp) > 300) {
http_response_code(400);
exit('Stale timestamp');
}
// 5. Compute expected HMAC
$secret = $secrets[$keyId];
$message = $timestamp . '.' . $rawBody;
$expected = hash_hmac('sha256', $message, $secret);
// 6. Validate: any v1 must match
$valid = false;
foreach ($hashes as $h) {
if (hash_equals($expected, $h)) {
$valid = true;
break;
}
}
if (!$valid) {
http_response_code(400);
exit('Invalid signature');
}
// 7. Signature verified → process payload
$data = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
/* …your business logic… */
// MUST reply with HTTP 200 so Fygaro marks the hook successful
http_response_code(200);
echo 'OK';
(For C#, Ruby, Go, etc., swap in equivalent header/body logic and HMAC calls.)
4. Best practices
Store secrets securely (environment variables, vaults).
Rotate secrets smoothly: keep both old & new until traffic shows only new hashes.
Always use constant-time comparison (hash_equals, timingSafeEqual, etc.).
Never log secrets or full HMACs—log only failure reasons and request IDs.
Respond HTTP 200 quickly on success; 200 is treated as “delivered” by Fygaro.
Payment Button Webhook
1. Webhook payload (POST body)
All monetary values are strings with two decimals.
{
"transactionId": "08d7360a-fc4b-46ad-a513-0a3d3fd3771c",
"reference": "ORDER-98765",
"customReference": "INV-2025-0420",
"authCode": "A12345",
"currency": "USD",
"amount": "59.99",
"createdAt": "2025-06-20T14:32:07Z",
// ↓ The next three blocks are OPTIONAL; they are sent only when data exists.
"card": {
"last4": "4242",
"expMonth": 12,
"expYear": 2030,
"brand": "visa"
},
"client": {
"email": "jane.doe@example.com",
"phone": "15551234567"
},
"billing": {
"country": { "name": "United States", "code": "US" },
"state": { "name": "California" },
"city": { "name": "Los Angeles" },
"locality": { "name": "Hollywood" },
"address": "123 Main St Apt 4B",
"postal_code": "90028"
},
"gratuity_amount": "5.00", // Present only when tips are captured
"jwt": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjEyMzRhYmNkIn0…" // LEGACY; will be removed
}
Field reference
Field | Type | Description |
---|---|---|
transactionId | string (UUID) | Fygaro’s immutable payment transaction ID. |
reference | string | Payment reference. |
customReference | string \ | null |
authCode | string \ | null |
currency | string | ISO 4217 code (e.g., USD). |
amount | string | Captured amount ("59.99"). |
createdAt | ISO-8601 string | UTC timestamp when the payment was created. |
gratuity_amount | string \ | absent |
card | object \ | absent |
client | object \ | absent |
billing | object \ | absent |
jwt | string | Deprecated. Present for backward compatibility only. |
Heads-up: begin migrating away from the jwt field now; future API versions will omit it.
card object
Field | Type | Description |
---|---|---|
last4 | string \ | null |
expMonth | integer \ | null |
expYear | integer \ | null |
brand | string | One of: visa, mastercard, amex, discover, diners, jcb, unionpay, maestro, or unknown. |
client object
Field | Type | Description |
---|---|---|
name | string | Shopper’s name. |
string \ | absent | |
phone | string \ | absent |
billing object
Field | Type | Description |
---|---|---|
country.name | string \ | null |
country.code | string \ | null |
state.name | string \ | null |
city.name | string \ | null |
locality.name | string \ | null |
address | string \ | null |
postal_code | string \ | null |
Need help? → support@fygaro.com
Updated on: 06/21/2025
Thank you!