# Verify signature

The `MONEI-Signature` header included in each signed request contains a timestamp and one or more signatures. The timestamp is prefixed by `t=`, and each signature is prefixed by a scheme. Schemes start with `v`, followed by an integer. Currently, the only valid live signature scheme is `v1`.

```
MONEI-Signature: t=1492774577,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
```

MONEI generates signatures using a hash-based message authentication code ([HMAC](https://en.wikipedia.org/wiki/HMAC)) with [SHA-256](https://en.wikipedia.org/wiki/SHA-2). To prevent [downgrade attacks](https://en.wikipedia.org/wiki/Downgrade_attack), you should ignore all schemes that are not `v1`.

## Verifying signatures using our official libraries[​](#verifying-signatures-using-our-official-libraries "Direct link to Verifying signatures using our official libraries")

Use one of our official libraries to verify signatures. You perform the verification by providing the request payload and the `MONEI-Signature` header. If verification fails, MONEI returns an error.

<!-- -->

* Node.js
* PHP
* Python

server.js

```
import express from 'express';

import {Monei, PaymentStatus} from '@monei-js/node-sdk';



// Set your api key. Remember to switch to your live api key in production!

// See your api key here: https://dashboard.monei.com/settings/api

const monei = new Monei('YOUR_API_KEY');



// This example uses Express to receive webhooks

const app = express();



// Endpoint to handle MONEI webhooks

app.post('/checkout/callback', express.raw({type: 'application/json'}), (req, res) => {

  const signature = req.headers['MONEI-Signature'];



  try {

    // Verify the signature using the raw request body and the header

    const payment = monei.verifySignature(req.body.toString(), signature);



    // Optional: Log the received payment status

    console.log(`Webhook received for Payment ID: ${payment.id}, Status: ${payment.status}`);



    // Update your order status based on the payment status

    if (payment.status === PaymentStatus.SUCCEEDED) {

      // Payment successful - fulfill the order

      console.log(`Payment ${payment.id} succeeded. Fulfilling order...`);

      // Update your database, send confirmation email, etc.

    } else if (payment.status === PaymentStatus.FAILED) {

      // Payment failed - notify the customer

      console.log(`Payment ${payment.id} failed. Notifying customer...`);

      // Log the failure, update your database, etc.

    } else if (payment.status === PaymentStatus.AUTHORIZED) {

      // Payment is authorized but not yet captured

      console.log(`Payment ${payment.id} authorized. Capture if needed.`);

      // You might want to capture it later using monei.payments.capture()

    } else if (payment.status === PaymentStatus.CANCELED) {

      // Payment was canceled by the user or system

      console.log(`Payment ${payment.id} was canceled.`);

      // Update your database accordingly

    } else {

      // Handle other potential statuses if necessary

      console.log(`Unhandled payment status: ${payment.status} for Payment ${payment.id}`);

    }



    // Acknowledge receipt of the webhook with a 200 OK status

    res.status(200).json({received: true});

  } catch (error) {

    // Handle signature verification failure

    console.error('Invalid webhook signature:', error.message);

    // Respond with 401 Unauthorized if the signature is invalid

    res.status(401).json({error: 'Invalid signature'});

  }

});



// Start the server

app.listen(3000, () => {

  console.log(`Server listening on port 3000`);

});
```

server.php

```
<?php

require_once(__DIR__ . '/vendor/autoload.php');



use Monei\\Model\\PaymentStatus;

use Monei\\ApiException;



// Replace YOUR_API_KEY with your actual MONEI API key

$monei = new Monei\\MoneiClient('YOUR_API_KEY');



// Parse raw body for signature verification

$rawBody = file_get_contents('php://input');

$signature = $_SERVER['HTTP_MONEI_SIGNATURE'] ?? '';



try {

    // Verify the signature

    // verifySignature returns the validated Payment object or throws ApiException

    $payment = $monei->verifySignature($rawBody, $signature);



    // Optional: Log the received payment status

    error_log('Webhook received for Payment ID: ' . $payment->getId() . ', Status: ' . $payment->getStatus());



    // Update your order status based on the payment status

    if ($payment->getStatus() === PaymentStatus::SUCCEEDED) {

        // Payment successful - fulfill the order

        error_log('Payment ' . $payment->getId() . ' succeeded. Fulfilling order...');

        // Update your database, send confirmation email, etc.

    } else if ($payment->getStatus() === PaymentStatus::FAILED) {

        // Payment failed - notify the customer

        error_log('Payment ' . $payment->getId() . ' failed. Notifying customer...');

        // Log the failure, update your database, etc.

    } else if ($payment->getStatus() === PaymentStatus::AUTHORIZED) {

        // Payment is authorized but not yet captured

        error_log('Payment ' . $payment->getId() . ' authorized. Capture if needed.');

        // You can capture it later using $monei->payments->capture(...)

    } else if ($payment->getStatus() === PaymentStatus::CANCELED) {

        // Payment was canceled

        error_log('Payment ' . $payment->getId() . ' was canceled.');

        // Update your database accordingly

    } else {

        // Handle other potential statuses if necessary

        error_log('Unhandled payment status: ' . $payment->getStatus() . ' for Payment ' . $payment->getId());

    }



    // Acknowledge receipt of the webhook with a 200 OK status

    http_response_code(200);

    header('Content-Type: application/json'); // Ensure JSON header

    echo json_encode(['received' => true]);



} catch (ApiException $e) {

    // Handle signature verification failure (ApiException specifically)

    error_log('Invalid webhook signature: ' . $e->getMessage());

    http_response_code(401); // Respond with 401 Unauthorized

    header('Content-Type: application/json'); // Ensure JSON header

    echo json_encode(['error' => 'Invalid signature']);

    exit(); // Stop script execution after sending error response

} catch (Exception $e) {

    // Handle any other unexpected errors during processing

    error_log('Webhook processing error: ' . $e->getMessage());

    http_response_code(500); // Respond with 500 Internal Server Error

    header('Content-Type: application/json');

    echo json_encode(['error' => 'Internal server error']);

    exit();

}

?>
```

server.py

```
import os

import Monei

from Monei.errors import SignatureVerificationError

from flask import Flask, request, abort, jsonify



# Replace YOUR_API_KEY with your actual MONEI API key

monei_client = Monei.MoneiClient(api_key="YOUR_API_KEY")



app = Flask(__name__)



# Update route and function name

@app.route('/checkout/callback', methods=['POST'])

def callback():

    signature = request.headers.get('MONEI-Signature') # Use correct header key



    # Check if signature header exists (optional but good practice)

    if not signature:

        print("MONEI-Signature header missing")

        return jsonify({'error': 'Missing MONEI-Signature header'}), 400



    try:

        # Verify the signature using raw request data (bytes)

        payment = monei_client.verify_signature(request.data, signature)



        # Optional: Log the received payment status

        payment_id = payment.get('id')

        payment_status = payment.get('status')

        print(f"Webhook received for Payment ID: {payment_id}, Status: {payment_status}")



        # Update your order status based on the payment status

        if payment_status == 'SUCCEEDED':

            # Payment successful - fulfill the order

            print(f"Payment {payment_id} succeeded. Fulfilling order...")

            # Update your database, send confirmation email, etc.

            pass # Placeholder from user snippet

        elif payment_status == 'FAILED':

            # Payment failed - notify the customer

            print(f"Payment {payment_id} failed. Notifying customer...")

            # Log the failure, update your database, etc.

            pass # Placeholder from user snippet

        elif payment_status == 'AUTHORIZED':

            # Payment is authorized but not yet captured

            print(f"Payment {payment_id} authorized. Capture if needed.")

            # You can capture it later

            pass # Placeholder from user snippet

        elif payment_status == 'CANCELED':

            # Payment was canceled

            print(f"Payment {payment_id} was canceled.")

            pass # Placeholder from user snippet

        else:

             print(f"Unhandled payment status: {payment_status} for Payment {payment_id}")



        # Acknowledge receipt of the webhook

        return jsonify({'received': True}), 200



    except SignatureVerificationError as e: # Catch specific signature error

        print(f"Invalid webhook signature: {e}")

        # Return 401 Unauthorized as requested

        return jsonify({'error': 'Invalid signature'}), 401

    except Exception as e: # Catch other potential errors during processing

        print(f"Webhook processing error: {e}")

        # Return 500 Internal Server Error for other issues

        return jsonify({'error': 'Internal server error'}), 500



if __name__ == '__main__':

    app.run(port=3000)
```

## Verifying signatures manually[​](#verifying-signatures-manually "Direct link to Verifying signatures manually")

### Step 1: Extract the timestamp and the signature from the header[​](#step-1-extract-the-timestamp-and-the-signature-from-the-header "Direct link to Step 1: Extract the timestamp and the signature from the header")

Split the header, using the `,` character as the separator, to get a list of elements. Then split each element, using the `=` character as the separator, to get a prefix and value pair.

The value for the prefix `t` corresponds to the timestamp, and `v1` corresponds to the signature (or signatures). You can discard all other elements.

### Step 2: Prepare the `signed_payload` string[​](#step-2-prepare-the-signed_payload-string "Direct link to step-2-prepare-the-signed_payload-string")

The `signed_payload` string is created by concatenating:

* The timestamp (as a string)
* The character `.`
* The actual JSON payload (i.e., the request body)

### Step 3: Determine the expected signature[​](#step-3-determine-the-expected-signature "Direct link to Step 3: Determine the expected signature")

Compute an HMAC with the SHA256 hash function. Use your account's API Key as the key, and use the `signed_payload` string as the message.

You can get your accounts password in [MONEI Dashboard → Settings → API](https://dashboard.monei.com/settings/api).

### Step 4: Compare the signatures[​](#step-4-compare-the-signatures "Direct link to Step 4: Compare the signatures")

Compare the signature in the header to the expected signature. For an equality match, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance.

To protect against timing attacks, use a constant-time string comparison to compare the expected signature to the received signature.
