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 | | |
Node ≥ 16 | |
TL;DR: Install the package, create a validator with your shared secret(s), callverify_signature
, and process the JSON only if it returnsTrue
(Python) ortrue
(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 timestampt=
and everyv1=
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 |
---|---|---|
| string (UUID) | Fygaro’s immutable payment transaction ID. |
| string | Payment reference. |
| string \ | null | Reference code set by you or your integration. |
| string \ | null | Authorization code from issuer. |
| string | ISO 4217 code (e.g., |
| string | Captured amount ( |
| ISO-8601 string | UTC timestamp when the payment was created. |
| string \ | absent | Captured tip amount when gratuity is enabled. |
| object \ | absent | Payment's card data. |
| object \ | absent | Shopper's contact data. |
| object \ | absent | Structured billing address. |
| 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 |
---|
| string \ | null | The four right-most digits of the card PAN. |
| integer \ | null | Expiration month 1-12. |
| integer \ | null | Full four-digit expiration year (e.g., |
| string | One of: |
client
object
Field | Type | Description |
---|---|---|
| string | Shopper’s name. |
| string \ | absent | Shopper’s email |
| string \ | absent | Shopper’s phone, if supplied during checkout. |
billing
object
Field | Type | Description |
---|
string \ | null | Country’s full name. | |
| string \ | null | ISO 3166-1 alpha-2 code ( |
string \ | null | State / province / region. | |
string \ | null | City / canton. | |
string \ | null | Neighborhood / district. | |
| string \ | null | Street address. |
| string \ | null | Postal / ZIP code. |
Need help? → support@fygaro.com
Updated on: 06/21/2025
Thank you!