Articles on: Payment Links
This article is also available in:

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:


  1. Define the default API key that will be used for signing each Hook request and will secure the information been shared
  2. 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:

  1. It must be a public path (accessible from the Internet).
  2. 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:


  1. Read the raw request body (bytes).
  2. Parse the Fygaro-Signature header → extract timestamp t= and every v1= hash.
  3. Parse Fygaro-Key-ID to know which secret to use.
  4. (Recommended) reject if abs(now - t) > 300 s (basic replay protection).
  5. Re-create the message exactly as Fygaro did:


   message = t + "." + raw_body


  1. Compute HMAC-SHA-256(secret, message) → hex.
  2. Constant-time compare the result against each v1; accept if any match.
  3. 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

Reference code set by you or your integration.

authCode

string \

null

Authorization code from issuer.

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

Captured tip amount when gratuity is enabled.

card

object \

absent

Payment's card data.

client

object \

absent

Shopper's contact data.

billing

object \

absent

Structured billing address.

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

The four right-most digits of the card PAN.

expMonth

integer \

null

Expiration month 1-12. null if unavailable.

expYear

integer \

null

Full four-digit expiration year (e.g., 2030). null if unavailable.

brand

string

One of: visa, mastercard, amex, discover, diners, jcb, unionpay, maestro, or unknown.



client object


Field

Type

Description

name

string

Shopper’s name.

email

string \

absent

Shopper’s email

phone

string \

absent

Shopper’s phone, if supplied during checkout.


billing object


Field

Type

Description

country.name

string \

null

Country’s full name.

country.code

string \

null

ISO 3166-1 alpha-2 code ("CR").

state.name

string \

null

State / province / region.

city.name

string \

null

City / canton.

locality.name

string \

null

Neighborhood / district.

address

string \

null

Street address.

postal_code

string \

null

Postal / ZIP code.



Need help? → support@fygaro.com

Updated on: 06/21/2025

Was this article helpful?

Share your feedback

Cancel

Thank you!