Notifications from Flywire to you

When you register an application, you will receive a Shared Secret that you should use to validate the payload of the received notification. For more information about secure notifications see Validating Notifications.

Flywire supports notifications via callbacks (also known as webhooks). These allow your system to receive real-time notifications about important events.

Types of Callbacks

There are different types of callbacks:

Validating Notifications

Validating notifications is optional, as it’s on your server side, but for security reasons it is recommended to validate all notifications.

How to validate a notification

To validate a notification, check its X-Flywire-Digest header. This value is generated by Flywire using your Shared Secret to encrypt the notification body. To verify the notification, generate the digest using the same method and compare it to the X-Flywire-Digest value in the header. If they match, the notification is legitimate and hasn't been tampered with.

  1. Retrieve the raw HTTP body of the notification you received.

    Make sure you use the raw HTTP body of the notification. You must generate the X-Flywire-Digest value using the exact payload you received in the notification. If you change the body in any way the values won't match later.

 

  1. Encrypt the received notification twice:

    1) Encrypt the raw HTTP body of the received notification with your Shared Secret using the SHA-256 algorithm.

    2) Take the result and encrypt it in Base64.

    The examples show you how to do this in different programming languages. In each example, exchange the shared_key with your Shared Secret and message_body with the raw HTTP body of the notification.

    digest = OpenSSL::Digest.new('sha256')
    encrypted_payload = OpenSSL::HMAC.digest(digest, shared_secret, notification_body)
    Base64.encode64(encrypted_payload).strip
    			
    # Step 1: Define the hashing algorithm to use for the HMAC.
    # This initializes the SHA-256 hashing mechanism.
    hash_algorithm = OpenSSL::Digest.new('sha256')
    
    # Step 2: Compute the HMAC in binary format.
    # This uses the shared secret and the message body to produce the HMAC signature.
    hmac_binary = OpenSSL::HMAC.digest(hash_algorithm, shared_secret, message_body)
    
    # Step 3: Encode the HMAC in Base64 for use in the X-Flywire-Digest header.
    x_flywire_digest = Base64.encode64(hmac_binary).strip
    public static string Digest(string shared_secret, string message_body)
    {
        // Step 1: Initialize the HMACSHA256 object with the shared secret.
        // This sets up the hashing algorithm (SHA-256) and the secret key for HMAC.
        using (var hmacsha256 = new HMACSHA256(Encoding.UTF8.GetBytes(shared_secret)))
        {
            // Step 2: Convert the message body into a byte array.
            // The message body is the message payload you want to hash
            var bytes = Encoding.UTF8.GetBytes(message_body);
    
            // Step 3: Compute the HMAC hash of the message body.
            // This generates the HMAC using the shared secret and the message body.
            var hashedBytes = hmacsha256.ComputeHash(bytes);
    
            // Step 4: Convert the hashed byte array into a Base64 string.
            return Convert.ToBase64String(hashedBytes);
        }
    }
    const crypto = require('crypto');
    
    function createDigest(shared_secret, message_body) {
        // Step 1: Initialize the HMAC with the SHA-256 algorithm and the shared secret.
        const hmac = crypto.createHmac('sha256', shared_secret);
        
        // Step 2: Update the HMAC with the message body.
        // The message body is the message payload you want to hash
        hmac.update(message_body);
    
        // Step 3: Get the HMAC digest and encode it in Base64.
        const digestHeader = hmac.digest('base64');
        
        return digestHeader;  // The `digestHeader` is the X-Flywire-Digest header.
    }

     

  2. Compare your Base64 string to the value in the X-Flywire-Digest parameter of the notification you received.

    If the values match, the notification came from Flywire and hasn't been changed by a third party.

    If the values don't match, you shouldn't trust the notification.

// Import the Express framework for setting up the server and the crypto library for hashing
const express = require("express");
const crypto = require("crypto");

const app = express(); // Create an Express app
const port = 3000;     // Define the port the server will listen on

// PART 1: Server Setup
// Listen for incoming requests on the specified port
app.listen(port, () => {
  console.log(`Example Flywire Webhook App listening on port ${port}`);
});

// PART 2: Middleware and Routing
// Use express.raw middleware to treat incoming JSON data as raw data (needed for HMAC verification)
app.use(express.raw({ type: "application/json" }));

// Define a route that will handle POST requests sent to /flywire-notification
app.post("/flywire-notification", (req, res) => {
  
  // Extract the raw request body and the digest header from the incoming request
  const payload = JSON.stringify(req.body);        // Convert raw request body to JSON string
  const requestDigest = req.header("X-Flywire-Digest"); // Retrieve the digest header from Flywire
  const sharedSecret = "YOUR SHARED SECRET";       // Define the shared secret for HMAC hashing

  // PART 3: Verification
  // Generate a digest using the payload and shared secret, matching Flywire's HMAC-SHA256 and Base64 encoding
  const serverDigest = digest(payload, sharedSecret);

  // Compare the digests to verify if the request is from Flywire
  if (requestDigest === serverDigest) {
    // Parse the payload and process data if verification is successful
    const data = JSON.parse(payload).data;
    console.log(`Processing notification: ${data.payment_id} - ${data.status}`);
  }

  // Send a 200 OK response back to Flywire indicating the request was processed
  res.sendStatus(200);
});

// Helper function to create a digest (HMAC-SHA256 + Base64) from the data and shared secret
function digest(data, sharedSecret) {
  const hmac = crypto.createHmac("sha256", sharedSecret); // Create HMAC using SHA256
  hmac.update(data);                                      // Update with the data (payload)

  return hmac.digest("base64");                           // Return the digest in Base64 format
}
<?php
  // Read the raw incoming data from the request body
  $data = file_get_contents('php://input');
  
  // Define the shared secret for verifying the request authenticity
  $sharedSecret = "YOUR SHARED SECRET";

  // Retrieve the 'X-Flywire-Digest' header from the incoming request
  $requestDigest = getHeader("X-Flywire-Digest");

  // Generate the server's own digest from the incoming data and shared secret
  $serverDigest = digest(trim($data), $sharedSecret);

  // Compare the request digest with the server's digest to verify authenticity
  if ($requestDigest == $serverDigest) {
      // Process notification (only if the verification passed)
  }

  // Function to create an HMAC-SHA256 digest and encode it in Base64
  function digest($data, $secret) {
      $digest = hex2bin(hash_hmac('sha256', $data, $secret)); // Create HMAC hash
      return trim(base64_encode($digest));                    // Encode the hash in Base64
  }

  // Helper function to retrieve a specific header from the request
  function getHeader($header) {
      foreach (getallheaders() as $name => $value) { // Loop through all headers
          if ($name == $header) {                    // Check if the header matches
              return $value;                         // Return the header value if matched
          }
      }
  }
?>