Payment Status Notifications

Payment status notifications are callbacks for payment key events, such as a payment status change. They enable you to track a payment while it makes its way through the payment workflow. Whenever a status change event occurs, for example when the payment status changes from Initiated to Processed, you'll get a notification to the URL you provided.

Using callbacks for payments is optional, but has several benefits:

  • Callbacks can be used to facilitate reconciliation of Flywire payments to accounting systems, ERPs, etc.

  • Callbacks provide real-time updates for payment statuses, no manual checks in Dashboard needed.

  • Callbacks are especially useful for direct debit payments where the payment capture will take a few working days and may be subject to future failure.

  • You can use callbacks to trigger processes that start after the payment is completed.

How do I get started?

Payment Statuses

image

A payment can have the following statuses and notifications:

Status Notification Description

Initiated

image initiated

The payment status initiated is the status for all new payments in Flywire. No funds have been received by Flywire at the initiated stage.

image authorized

Only for Pre-Authorization Payments.

The payment has been authorized and is now in the holding period.

image adjusted

Only for Pre-Authorization Payments.

The amount that is being held on the card has been adjusted.

Processed

image processed

The payment status goes from initiated to processed when Flywire has the confirmation that the funds have been received or captured via one of these ways:

  • Bank transfer: funds have been received in a Flywire bank account.

  • Card payments: funds have been captured in payer’s card.

  • Direct debit: instructions to capture the funds are in progress and correct.

Guaranteed

image guaranteed

The status changes to guaranteed when funds are received by Flywire and all the necessary validations for that payment method and corridor are successful (since Flywire performs a security check on every payment once the funds have been received).

At this point, you are guaranteed that Flywire will send you the funds.

Delivered

image delivered

The status changes from guaranteed to delivered when Flywire sent you the funds in your daily batch disbursement. Payments are updated to delivered at a set time each day.

The status delivered is the end stage, the status of a delivered payment can't change.

Failed

image failed

The status failed can only occur for card , online (PayPal etc.) or direct debit payments. It occurs when there’s an issue to capture the funds. The notification will contain the reason for the failure. Any subsequent attempt will either trigger another failed status or alternatively processed if the retry succeeds.

Cancelled

image cancelled

The payment status Cancelled means that the payment will not be processed. If Flywire already received the funds, Flywire will return the funds to the payer.

Reversed

image reversed

This status has different meaning depending on the context:

Payment Status Notifications

The payment status initiated is the status for all new payments in Flywire. No funds have been received by Flywire at the initiated stage.

 

data object

payment_method object

fields object

These are the fields of the recipient (also called "custom fields of a portal") . The fields are returned as pairs of the field id (for example booking_reference) and the value (for example ID123456). Which fields are returned depends on the individual configuration of the recipient.

payer object

The payer information is not automatically included. Returning payer information is disabled by default and needs to be enabled by Flywire.

Please reach out to your Flywire contact if you require payer information to be returned.


  "event_type": "initiated",
  "event_date": "2021-05-20T11:24:45Z",
  "event_resource": "payments",
  "data": {
    "payment_id": "PTU146221637",
    "amount_from": "4225",
    "currency_from": "EUR",
    "amount_to": "5000",
    "currency_to": "USD",
    "status": "initiated",
    "expiration_date": "2021-05-31T11:24:45Z",
    "external_reference": "a-reference",
    "country": "ES",
    "payment_method": {
      "type": "card"
    },
    "recurring_id": "IPTQQ18ECD5B31AB",
	"fields":{ 
		"booking_reference": "ID123456",
		"booking_description": "A description"
	}, 
	"payer": {
		"first_name": "Peter",
		"last_name": "Payer",
		"middle_name": null,		
		"address1": "789 Calle Mayor",
		"address2": null,
		"city": "Madrid",
		"country": "ES",
		"state": null,
		"zip": "28013",
		"phone": "0034912345678",
		"email": "[email protected]"
    },
  }
}

Only for Pre-Authorization Payments.

The payment has been authorized and is now in the holding period.

 

data object

payment_method object

fields object

These are the fields of the recipient (also called "custom fields of a portal") . The fields are returned as pairs of the field id (for example booking_reference) and the value (for example ID123456). Which fields are returned depends on the individual configuration of the recipient.

payer object

The payer information is not automatically included. Returning payer information is disabled by default and needs to be enabled by Flywire.

Please reach out to your Flywire contact if you require payer information to be returned.

{
  "event_type": "authorized",
  "event_date": "2024-03-20T11:33:02Z",
  "event_resource": "charges",
  "data": {
    "payment_id": "PTU146221637",
    "amount_from": "4800",
    "currency_from": "EUR",
    "amount_to": "5000",
    "currency_to": "USD",
    "status": "authorized",
    "expiration_date": "2024-03-28T11:33:02Z",
    "external_reference": "a-reference",
    "country": "ES",
    "payment_method": {
      "type": "card"
    },
	"fields":{ 
		"booking_reference": "ID123456",
		"booking_description": "A description"
	}, 
	"payer": {
		"first_name": "Peter",
		"last_name": "Payer",
		"middle_name": null,		
		"address1": "789 Calle Mayor",
		"address2": null,
		"city": "Madrid",
		"country": "ES",
		"state": null,
		"zip": "28013",
		"phone": "0034912345678",
		"email": "[email protected]"
    },						
  }
}

Only for Pre-Authorization Payments.

The amount that is being held on the card has been adjusted.

 

dataobject

payment_method object

fields object

These are the fields of the recipient (also called "custom fields of a portal") . The fields are returned as pairs of the field id (for example booking_reference) and the value (for example ID123456). Which fields are returned depends on the individual configuration of the recipient.

payer object

The payer information is not automatically included. Returning payer information is disabled by default and needs to be enabled by Flywire.

Please reach out to your Flywire contact if you require payer information to be returned.

{
  "event_type": "adjusted",
  "event_date": "2024-03-20T11:33:02Z",
  "event_resource": "charges",
  "data": {
    "payment_id": "PTU146221637",
    "amount_from": "4800",
    "currency_from": "EUR",
    "amount_to": "5000",
    "currency_to": "USD",
    "status": "adjusted",
    "expiration_date": "2024-03-28T11:33:02Z",
    "external_reference": "a-reference",
    "country": "ES",
    "payment_method": {
      "type": "card"
    },
	"fields":{ 
		"booking_reference": "ID123456",
		"booking_description": "A description"
	}, 
	"payer": {
		"first_name": "Peter",
		"last_name": "Payer",
		"middle_name": null,		
		"address1": "789 Calle Mayor",
		"address2": null,
		"city": "Madrid",
		"country": "ES",
		"state": null,
		"zip": "28013",
		"phone": "0034912345678",
		"email": "[email protected]"
    },						
  }
}

The payment status goes from initiated to processed when Flywire has the confirmation that the funds have been received or captured via one of these ways:

  • Bank transfer: funds have been received in a Flywire bank account.

  • Card payments: funds have been captured in payer’s card.

  • Direct debit: instructions to capture the funds are in progress and correct.

 

data object

payment_method object

fields object

These are the fields of the recipient (also called "custom fields of a portal") . The fields are returned as pairs of the field id (for example booking_reference) and the value (for example ID123456). Which fields are returned depends on the individual configuration of the recipient.

payer object

The payer information is not automatically included. Returning payer information is disabled by default and needs to be enabled by Flywire.

Please reach out to your Flywire contact if you require payer information to be returned.

{
  "event_type": "processed",
  "event_date": "2021-05-20T11:25:02Z",
  "event_resource": "charges",
  "data": {
    "payment_id": "TQQ146221637",
    "amount_from": "4225",
    "currency_from": "EUR",
    "amount_to": "5000",
    "currency_to": "USD",
    "status": "processed",
    "expiration_date": "2021-05-31T11:24:45Z",
    "external_reference": "a-reference",
    "country": "ES",
    "payment_method": {
      "type": "card",
      "brand": "visa",
      "card_classification": "credit",
      "card_expiration": "08/2025",
      "last_four_digits": "3878" 
    },
    "recurring_id": "IPTQQ18ECD5B31AB",
	"fields":{ 
		"booking_reference": "ID123456",
		"booking_description": "A description"
	}, 
	"payer": {
		"first_name": "Peter",
		"last_name": "Payer",
		"middle_name": null,		
		"address1": "789 Calle Mayor",
		"address2": null,
		"city": "Madrid",
		"country": "ES",
		"state": null,
		"zip": "28013",
		"phone": "0034912345678",
		"email": "[email protected]"
    },
  }
}
									

The status changes to guaranteed when funds are received by Flywire and all the necessary validations for that payment method and corridor are successful (since Flywire performs a security check on every payment once the funds have been received).

At this point, you are guaranteed that Flywire will send you the funds.

data object

payment_method object

fields object

These are the fields of the recipient (also called "custom fields of a portal") . The fields are returned as pairs of the field id (for example booking_reference) and the value (for example ID123456). Which fields are returned depends on the individual configuration of the recipient.

payer object

The payer information is not automatically included. Returning payer information is disabled by default and needs to be enabled by Flywire.

Please reach out to your Flywire contact if you require payer information to be returned.

{
  "event_type": "guaranteed",
  "event_date": "2021-05-20T11:25:05Z",
  "event_resource": "payments",
  "data": {
    "payment_id": "PTU146221637",
    "amount_from": "4225",
    "currency_from": "EUR",
    "amount_to": "5000",
    "currency_to": "USD",
    "status": "guaranteed",
    "expiration_date": "2021-05-31T11:24:45Z",
    "external_reference": "a-reference",
    "country": "ES",
    "payment_method": {
      "type": "card",
      "brand": "visa",
      "card_classification": "credit",
      "card_expiration": "08/2025",
      "last_four_digits": "3878" 
    },
    "recurring_id": "IPTQQ18ECD5B31AB",
	"fields":{ 
		"booking_reference": "ID123456",
		"booking_description": "A description"
	}, 
	"payer": {
		"first_name": "Peter",
		"last_name": "Payer",
		"middle_name": null,		
		"address1": "789 Calle Mayor",
		"address2": null,
		"city": "Madrid",
		"country": "ES",
		"state": null,
		"zip": "28013",
		"phone": "0034912345678",
		"email": "[email protected]"
    },
  }
}

The status changes from guaranteed to delivered when Flywire sent you the funds in your daily batch disbursement. Payments are updated to delivered at a set time each day.

The status delivered is the end stage, the status of a delivered payment can't change.

 

data object

payment_method object

payouts array

fields object

These are the fields of the recipient (also called "custom fields of a portal") . The fields are returned as pairs of the field id (for example booking_reference) and the value (for example ID123456). Which fields are returned depends on the individual configuration of the recipient.

payer object

The payer information is not automatically included. Returning payer information is disabled by default and needs to be enabled by Flywire.

Please reach out to your Flywire contact if you require payer information to be returned.

{
  "event_type": "delivered",
  "event_date": "2021-05-20T11:48:02Z",
  "event_resource": "payments",
  "data": {
    "payment_id": "TQQ146221637",
    "amount_from": "4225",
    "currency_from": "EUR",
    "amount_to": "5000",
    "currency_to": "USD",
    "status": "delivered",
    "expiration_date": "2021-05-31T11:24:45Z",
    "external_reference": "a-reference",
    "country": "ES",
    "payment_method": {
      "type": "card",
      "brand": "visa",
      "card_classification": "credit",
      "card_expiration": "08/2025",
      "last_four_digits": "3878"
     },
    "payouts": [
      {
        "portal_code": "TQQ",
        "currency": "GBP",
        "amount": "28300",
        "disbursement_id": "SANDBOX-TQQ2024-04-18-1713458596"
      }
    ],
    "recurring_id": "IPTQQ18ECD5B31AB",						
	"fields":{ 
		"booking_reference": "ID123456",
		"booking_description": "A description"
	}, 
	"payer": {
		"first_name": "Peter",
		"last_name": "Payer",
		"middle_name": null,		
		"address1": "789 Calle Mayor",
		"address2": null,
		"city": "Madrid",
		"country": "ES",
		"state": null,
		"zip": "28013",
		"phone": "0034912345678",
		"email": "[email protected]"
    },
  }
}

The status failed can only occur for card , online (PayPal etc.) or direct debit payments. It occurs when there’s an issue to capture the funds. The notification will contain the reason for the failure. Any subsequent attempt will either trigger another failed status or alternatively processed if the retry succeeds.

For any new failed charge attempt, Flywire will send a new failed payment status notification. If the retry succeeds, Flywire will send a processed notification. If the payer retries with a different payment method, the information in the payment_method object will change.

data object

payment_method object

fields object

These are the fields of the recipient (also called "custom fields of a portal") . The fields are returned as pairs of the field id (for example booking_reference) and the value (for example ID123456). Which fields are returned depends on the individual configuration of the recipient.

payer object

The payer information is not automatically included. Returning payer information is disabled by default and needs to be enabled by Flywire.

Please reach out to your Flywire contact if you require payer information to be returned.

{
  "event_type": "failed",
  "event_date": "2022-02-21T11:15:34Z",
  "event_resource": "charges",
  "data": {
	"payment_id": "MGT670199181",
	"amount_from": "420",
	"currency_from": "USD",
	"amount_to": "420",
	"currency_to": "USD",
	"status": "failed",
	"expiration_date": "2022-02-23T11:12:19Z",
	"external_reference": "Callback ID 1234",
	"country": "US",
	"payment_method": {
  		"type": "card",
  		"brand": "visa",
  		"card_classification": "credit",
   		"card_expiration": "01/2035",
  		"last_four_digits": "5555"
		},
	"reason": "Your transaction has been declined by your bank. Please try increasing the available balance of your account, use a different card/bank account or contact your bank for further assistance.",
	"reason_code": "012",
    "client_reason": "Not enough balance",
    "recurring_id": "IPTQQ18ECD5B31AB",						
	"fields":{ 
		"booking_reference": "ID123456",
		"booking_description": "A description"
	}, 
	"payer": {
		"first_name": "Peter",
		"last_name": "Payer",
		"middle_name": null,		
		"address1": "789 Calle Mayor",
		"address2": null,
		"city": "Madrid",
		"country": "ES",
		"state": null,
		"zip": "28013",
		"phone": "0034912345678",
		"email": "[email protected]"
    },
  }
}

The payment status Cancelled means that the payment will not be processed. If Flywire already received the funds, Flywire will return the funds to the payer.

 

data object

payment_method object

fields object

These are the fields of the recipient (also called "custom fields of a portal") . The fields are returned as pairs of the field id (for example booking_reference) and the value (for example ID123456). Which fields are returned depends on the individual configuration of the recipient.

payer object

The payer information is not automatically included. Returning payer information is disabled by default and needs to be enabled by Flywire.

Please reach out to your Flywire contact if you require payer information to be returned.

{
  "event_type": "cancelled",
  "event_date": "2021-05-20T11:33:02Z",
  "event_resource": "payments",
  "data": {
    "payment_id": "PTU146221637",
    "amount_from": "4225",
    "currency_from": "EUR",
    "amount_to": "5000",
    "currency_to": "USD",
    "status": "cancelled",
    "expiration_date": "2021-05-31T11:24:45Z",
    "external_reference": "a-reference",
    "country": "ES",
    "cancellation_reason": "cancelled_by_user",
    "payment_method": {
      "type": "card"   
    },
    "recurring_id": "IPTQQ18ECD5B31AB",	
	"fields":{ 
		"booking_reference": "ID123456",
		"booking_description": "A description"
	}, 
	"payer": {
		"first_name": "Peter",
		"last_name": "Payer",
		"middle_name": null,		
		"address1": "789 Calle Mayor",
		"address2": null,
		"city": "Madrid",
		"country": "ES",
		"state": null,
		"zip": "28013",
		"phone": "0034912345678",
		"email": "[email protected]"
    },											
  }
}

There are two types of reversed callbacks:

This reversed type means a refund for the payment has been finished. It can either be a partial or a full refund. If there are multiple partial refunds for a payment, you will receive a notification for each new finished refund. Keep in mind that there can only be one refund at a time for a payment.

This status tells you that a refund has successfully been completed for the payment.

data object

payment_method object

reversed_amount object

currency object

fields object

These are the fields of the recipient (also called "custom fields of a portal") . The fields are returned as pairs of the field id (for example booking_reference) and the value (for example ID123456). Which fields are returned depends on the individual configuration of the recipient.

payer object

The payer information is not automatically included. Returning payer information is disabled by default and needs to be enabled by Flywire.

Please reach out to your Flywire contact if you require payer information to be returned.

{
  "event_type": "reversed",
  "event_date": "2021-05-20T11:33:02Z",
  "event_resource": "payments",
  "data": {
    "payment_id": "PTU146221637",
    "amount_from": "4800",
    "currency_from": "EUR",
    "amount_to": "50000",
    "currency_to": "USD",
    "status": "reversed",
    "expiration_date": "2021-05-31T11:24:45Z",
    "external_reference": "a-reference",
    "country": "ES",
    "payment_method": {
      "type": "card"
    },
    "reversed_type": "refund",
    "entity_id": "RPTUDD91239F",
    "reversed_amount": {
        "value": "10000",
        "currency": {
            "code": "USD",
            "subunit_to_unit": "100"
        }
    },
    "reason": "Refund finished",
    "reason_code": "106"
    "recurring_id": "IPTQQ18ECD5B31AB",
	"fields":{ 
		"booking_reference": "ID123456",
		"booking_description": "A description"
	}, 
	"payer": {
		"first_name": "Peter",
		"last_name": "Payer",
		"middle_name": null,		
		"address1": "789 Calle Mayor",
		"address2": null,
		"city": "Madrid",
		"country": "ES",
		"state": null,
		"zip": "28013",
		"phone": "0034912345678",
		"email": "[email protected]"
    },
  }
}

This reversed type is only relevant for direct debits payments.

The status occurs when you (the client) received the funds from Flywire but Flywire did not receive the funds from your payer's bank account.

From a technical side this means:

  • The payment went into the delivered status (which means you received the funds from Flywire).

  • Then the payment fails and remains unpaid (which means Flywire did not receive the funds).

  • Then Flywire will start a recovery process and the payment status will change from delivered to reversed.

    Note that if the payment fails in an earlier status (processed or guaranteed) the payment will be cancelled instead of reversed.

data object

payment_method object

reversed_amount object

currency object

fields object

These are the fields of the recipient (also called "custom fields of a portal") . The fields are returned as pairs of the field id (for example booking_reference) and the value (for example ID123456). Which fields are returned depends on the individual configuration of the recipient.

payer object

The payer information is not automatically included. Returning payer information is disabled by default and needs to be enabled by Flywire.

Please reach out to your Flywire contact if you require payer information to be returned.

{
   "event_type":"reversed",
   "event_date":"2023-04-28T12:02:23Z",
   "event_resource":"payments",
   "data":{
      "payment_id":"ALA356132734",
      "amount_from":"14700",
      "currency_from":"USD",
      "amount_to":"14700",
      "currency_to":"USD",
      "status":"reversed",
      "expiration_date":"2023-05-09T12:01:22Z",
      "external_reference":"0a78cc69-585f-4250-b368-1fa990a463b3",
      "country":"US",
      "payment_method":{
         "type":"direct_debit"
      },
	  "reversed_type": "unpaid",
	  "entity_id": "REV_ALA356132734",
	  "reversed_amount": {
			"value": "14700",
			"currency": {
			"code": "USD",
			"subunit_to_unit": "100"
			}
		},
		"reason": "Your transaction has been declined by your bank. Please try increasing the available balance of your account, use a different card/bank account or contact your bank for further assistance.",
		"reason_code": "012",
    	"recurring_id": "IPALA356132734",
	"fields":{ 
		"booking_reference": "ID123456",
		"booking_description": "A description"
	}, 
	"payer": {
		"first_name": "Peter",
		"last_name": "Payer",
		"middle_name": null,		
		"address1": "456 Elm Street",
		"address2": null,
		"city": "Springfield",
		"country": "US",
		"state": "IL",
		"zip": "62701",
		"phone": "0012175556789",
		"email": "[email protected]"
    },
  }
}

How to set up Payment Status Notifications

This information only applies to callbacks that return information about payments. Callbacks for Payment Requests are set up in a different way.

Types of Notification URLs

There are two different URLs for receiving callbacks:

Static URL

A static callback URL is a fixed URL defined in your portal. All payments for this portal will send callbacks to this URL.

The static callback URL has been defined when your portal was set up. If you don't use a static callback URL yet and want to start using it, please reach out to your Flywire contact.

Dynamic URL

A dynamic callback URL is a URL that you define when you create the payment. Since this URL can be different for every payment you create, it is called dynamic.

For image Pay-By-Link and imageCheckout , you define the URL via the callbackUrl parameter.

If you want to receive callbacks, you must provide a callback URL and a callback ID, otherwise no callbacks will be triggered. You also must set the callback version to 2.

You cannot provide a dynamic callback URL for payments that are part of a Payment Request. To receive callbacks about those payments, you have to use the static URL. Alternatively, you can use callback notifications for Payment Request status updates, but note that the content of those callbacks are different from payment status callbacks.

How defining static and dynamic URLs affect callbacks

image = not set   image = set

 

Static
URL
Dynamic
URL
Result
image image You won't receive callbacks .
image image

You'll receive callbacks to your static URL.

You'll also receive callbacks for payments that are part of Payment Requests.

image image

You'll receive callbacks to both URLs.

You'll also receive callbacks for payments that are part of Payment Requests, but only to the static URL.

image image

You'll receive callbacks to your dynamic URL

You will not receive callbacks for payments that are part of Payment Requests since there is no static URL.

Parameters for Payment Status Notifications Settings

You'll encounter two parameters that influence the payment status notifications for a payment whenever you are creating a payment.

 

Parameter Description

external_reference

string

The external reference helps you to identify a payment, since the Flywire-generated payment reference might not be the way you typically identify payments. With the external reference, you can enter your own identifier, such as an ID or invoice number.

The external reference is included in all status notifications to help you map a payment to a callback notification. (see Payment Status Notifications)

notifications_url

string (url)

The notifications URL enables you to receive callbacks about the payment status (see Payment Status Notifications).

The notifications URL is the dynamic URL for receiving callbacks.

If you are using the Checkout or Pay-By-Link integration:

If you want to receive callbacks, you must provide a callback URL and a callback ID, otherwise no callbacks will be triggered. You also must set the callback version to 2.

Failed notifications: What happens if a callback can't get delivered?

When Flywire tries to send a notification to the callback URL you provided and receives an error, there will automatically three re-attempts:

  • First time with a delay of 180 seconds (3 minutes)

  • Second time with a delay of 1800 seconds (30 minutes)

  • Third time with a delay of 10800 seconds (3 hours)

Failed callback report

Flywire logs all failed callback sending attempts. If after the third try sending the callback is still unsuccessful, Flywire sends a failed callback report to an approved contact for review. You as a client must provide the contact details for someone to receive the failed callback report.

Validating Payment Status 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
          }
      }
  }
?>