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:

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:

RuntimePackageDocs & Install
Python ≥ 3.8fygaro-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



FieldTypeDescription
transactionIdstring (UUID)Fygaro’s immutable payment transaction ID.
referencestringPayment reference.
customReferencestring \null
authCodestring \null
currencystringISO 4217 code (e.g., USD).
amountstringCaptured amount ("59.99").
createdAtISO-8601 stringUTC timestamp when the payment was created.
gratuity_amountstring \absent
cardobject \absent
clientobject \absent
billingobject \absent
jwtstringDeprecated. Present for backward compatibility only.


Heads-up: begin migrating away from the jwt field now; future API versions will omit it.

card object



FieldTypeDescription
last4string \null
expMonthinteger \null
expYearinteger \null
brandstringOne of: visa, mastercard, amex, discover, diners, jcb, unionpay, maestro, or unknown.



client object



FieldTypeDescription
namestringShopper’s name.
emailstring \absent
phonestring \absent


billing object



FieldTypeDescription
country.namestring \null
country.codestring \null
state.namestring \null
city.namestring \null
locality.namestring \null
addressstring \null
postal_codestring \null



Need help? → support@fygaro.com

Updated on: 06/21/2025

Was this article helpful?

Share your feedback

Cancel

Thank you!