Knowledge base

Know more about the notification webhooks and information that can be vital in processing the notifications effectively.

Domain whitelisting

Based on your network and security setup, you might need to configure your firewalls' whitelist to receive notifications from us. To achieve this, you can either:

  • You do not need to add a long list of IP addresses from us; instead, whitelist our domain pay-nn.de if you are network configured using domain whitelisting.
  • Perform DNS lookup for the domain pay-nn.de regularly to resolve our IP addresses. We recommend not hardcoding the resolved IP addresses to the whitelist.

Retrial mechanism

Novalnet's inbuilt retrial process allows you to receive the webhook data even if the initial notification has failed. The failure could be for any reason, server unreachable, site maintenance, site errors, etc.; nevertheless, Novalnet's server identifies the failed notifications and resend the data at the end of the day.

A final retrial will be performed the next day if the notification still can't be notified to your webhook endpoint.

If you have any inquiries about the re-transmission of the failed notifications, please contact the Novalnet Support Team.

Authentication mechanism

It is certain that every notification reaching your webhook endpoint should be authenticated, which lowers the risk of data manipulation. The comparison against the event makes the authentication event.checksum received in the notification response. Here you need to re-generate your checksum value.

The checksum generation differs based on the notification event from Novalnet as mentioned below:

Transaction events: Every notification event involves a transaction.

Non-Transaction events: This category includes the events AFFILIATE_CREATION, AFFILIATE_ACTIVATION, CALL_N_PAY and PAY_BY_LINK, where no transaction is involved.

Sample Script

Please refer to the Novalnet sample pre-built solution

Sample Script
<?php

/**
 * Novalnet Webhooks.
 *
 * @version 2.0.0
 */
class NovalnetWebhooks
{

    /**
     * Allowed host from Novalnet.
     *
     * @var string
     */
    protected $novalnet_host_name = 'pay-nn.de';

    /**
     * Mandatory Parameters.
     *
     * @var array
     */
    protected $mandatory = [
        'event'       => [
            'type',
            'checksum',
            'tid',
        ],
        'merchant'    => [
            'vendor',
            'project',
        ],
        'result'      => [
            'status',
        ],
        'transaction' => [
            'tid',
            'payment_type',
            'status',
        ],
    ];

	/**
     * Mandatory Parameters for affiliate creation & activation.
     *
     * @var array
     */
    protected $affiliate_mandatory = [
        'event'     => [
            'type',
            'checksum',
        ],
        'merchant'  => [
            'vendor',
            'project',
        ],
        'affiliate' => [
            'vendor'
        ],
    ];
    
    /**
     * Callback test mode.
     *
     * @var int
     */
    protected $test_mode;

    /**
     * Request parameters.
     *
     * @var array
     */
    protected $event_data = [];

    /**
     * Your payment access key value
     *
     * @var string
     */
    protected $payment_access_key;

    /**
     * Order reference values.
     *
     * @var array
     */
    protected $order_reference = [];

    /**
     * Recived Event type.
     *
     * @var string
     */
    protected $event_type;

    /**
     * Recived Event TID.
     *
     * @var int
     */
    protected $event_tid;

    /**
     * Recived Event parent TID.
     *
     * @var int
     */
    protected $parent_tid;

    /**
     * Novalnet_Webhooks constructor.
     */
    public function __construct()
    {
        // Authenticate request host.
        $this->authenticateEventData();
        
        // Get request parameters.
        $this->validateEventData();
        
        // Your payment access key value.
        $this->payment_access_key  = '###YOUR_PAYMENT_ACCESS_KEY###';
        
        // Validate checksum
        $this->validateChecksum();
    
        // Set Event data.
        $this->event_type = $this->event_data ['event'] ['type'];
        $this->event_tid  = !empty($this->event_data ['event'] ['tid']) ? $this->event_data ['event'] ['tid'] : '';

        $this->parent_tid = $this->event_tid;
        if (!empty($this->event_data ['event'] ['parent_tid'])) {
            $this->parent_tid = $this->event_data ['event'] ['parent_tid'];
        }

        // Get order reference.
        $this->order_reference = $this->getOrderReference();

        // Order number check.
        if (!empty($this->event_data ['transaction'] ['order_no']) && $this->order_reference ['order_no'] !== $this->event_data ['transaction'] ['order_no']) {
            $this->displayMessage(['message' => 'Order reference not matching.']);
        }

        switch ($this->event_type) {
            case "PAYMENT":
                // Handle Initial PAYMENT notification (incl. communication failure, Authorization).
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type) for the TID: $this->event_tid."]);
                break;
                
            case "TRANSACTION_CAPTURE":
                // Handle TRANSACTION_CAPTURE notification. It confirms the successful capture of the transaction.
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type) for the TID: $this->event_tid."]);
                break;
                
            case "TRANSACTION_CANCEL":
                // Handle TRANSACTION_CANCEL notification. It confirms the successful cancelation of the transaction.
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type) for the TID: $this->event_tid."]);
                break;
                
            case "TRANSACTION_REFUND":
                // Handle TRANSACTION_REFUND notification. It confirms the successful refund (partial/full) of the transaction.
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type) for the TID: $this->event_tid."]);
                break;
                
            case "TRANSACTION_UPDATE":
                // Handle TRANSACTION_UPDATE notification. It confirms the successful update (payment status/amount/due date/order number) of the transaction.
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type) for the TID: $this->event_tid."]);
                break;
            
            case "CREDIT":
                // Handle CREDIT notification. It confirms that the payment (partial/full) for the transaction was received
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type) for the TID: $this->parent_tid and the new reference TID was $this->event_tid"]);
                break;
                
            case "CHARGEBACK":
                // Handle CHARGEBACK notification. It confirms that the chargeback (for Credit Card) has been received for the transaction.
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type) for the TID: $this->parent_tid and the new reference TID was $this->event_tid"]);
                break;

            case "INSTALMENT":
                // Handle INSTALMENT notification. It confirms the successful creation of the followup instalment transaction.
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type) for the TID: $this->parent_tid and the new reference TID was $this->event_tid"]);
                break;
                
            case "INSTALMENT_CANCEL":
                // Handle SUBSCRIPTION_CANCEL notification. It confirms the successful cancelation of the subscription.
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type) for the TID: $this->event_tid."]);
                break;
                
            case "RENEWAL":
                // Handle RENEWAL notification. It confirms the successful creation of the subscription renewal transaction.
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type) for the TID: $this->parent_tid and the new reference TID was $this->event_tid"]);
                break;
            
            case "SUBSCRIPTION_SUSPEND":
                // Handle SUBSCRIPTION_SUSPEND notification. It confirms the successful suspend (pause) of the subscription.
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type) for the TID: $this->event_tid."]);
                break;
                
            case "SUBSCRIPTION_REACTIVATE":
                // Handle SUBSCRIPTION_REACTIVATE notification. It confirms the successful reactivation (resume) of the subscription.
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type) for the TID: $this->event_tid."]);
                break;
            
            case "SUBSCRIPTION_CANCEL":
                // Handle SUBSCRIPTION_CANCEL notification. It confirms the successful cancelation of the subscription.
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type) for the TID: $this->event_tid."]);
                break;

            case "SUBSCRIPTION_UPDATE":
                // Handle SUBSCRIPTION_UPDATE notification. It confirms the successful update (payment type/amount/cycle date/order number) of the subscription.
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type) for the TID: $this->event_tid."]);
                break;
            
            case "AFFILIATE":
                // Handle AFFILIATE notification. It confirms the successful creation of the followup affiliate transaction.
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type) for the TID: $this->parent_tid and the new reference TID was $this->event_tid"]);
                break;
                
            case "AFFILIATE_CREATION":
                // Handle AFFILIATE_CREATION notification. It confirms the successful creation of the affiliate
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type)"]);
                break;
                
            case "AFFILIATE_ACTIVATION":
                // Handle AFFILIATE_ACTIVATION notification. It confirms the successful affiliate account activation
                $this->displayMessage(['message' => "The webhook notification received ($this->event_type)"]);
                break;    
                
            default:
                $this->displayMessage(['message' => "The webhook notification has been received for the unhandled EVENT type($this->event_type)"]);
        }
    }
    
    /**
     * Authenticate server request
     *
     * @return void
     */
    public function authenticateEventData()
    {

        // Backend callback option.
        $this->test_mode = '###TEST_MODE###'; // Your shop test mode value
        
        // Authenticating the server request based on IP.
        $request_received_ip = $this->getIpAddress();
        
        // Host based validation
        if (!empty($this->novalnet_host_name)) {
            $novalnet_host_ip  = gethostbyname($this->novalnet_host_name);
            if (!empty($novalnet_host_ip) && ! empty($request_received_ip)) {
                if ($novalnet_host_ip !== $request_received_ip && ! $this->test_mode) {
                    $this->displayMessage(['message' => 'Unauthorised access from the IP ' . $request_received_ip]);
                }
            } else {
                $this->displayMessage([ 'message' => 'Unauthorised access from the IP. Host/recieved IP is empty' ]);
            }
        } else {
            $this->displayMessage([ 'message' => 'Unauthorised access from the IP. Novalnet Host name is empty' ]);
        }
    }

    /**
     * Validate server request
     *
     * @return void
     */
    public function validateEventData()
    {
        try {
        $this->event_data = json_decode(file_get_contents('php://input'), true);
        } catch (Exception $e) {
            $this->displayMessage(['message' => "Received data is not in the JSON format $e"]);
        }
        
        if (! empty($this->event_data ['custom'] ['shop_invoked'])) {
            $this->displayMessage([ 'message' => 'Process already handled in the shop.' ]);
        }
        
        if (! empty($this->event_data ['event'] ['type']) && in_array($this->event_data ['event'] ['type'], ['AFFILIATE_CREATION', 'AFFILIATE_ACTIVATION'])) {
			$mandatory = $this->affiliate_mandatory;
        } else {
			$mandatory = $this->mandatory;
        }
        // Validate request parameters.
         foreach ($mandatory as $category => $parameters) {
            if (empty($this->event_data [ $category ])) {
                // Could be a possible manipulation in the notification data.
                $this->displayMessage([ 'message' => "Required parameter category($category) not received" ]);
            } elseif (! empty($parameters)) {
                foreach ($parameters as $parameter) {
                    if (empty($this->event_data [ $category ] [ $parameter ])) {
                        // Could be a possible manipulation in the notification data.
                        $this->displayMessage([ 'message' => "Required parameter($parameter) in the category($category) not received" ]);
                    } elseif (in_array($parameter, [ 'tid', 'parent_tid' ], true) && ! preg_match('/^\d{17}$/', $this->event_data [ $category ] [ $parameter ])) {
                        $this->displayMessage([ 'message' => "Invalid TID received in the category($category) not received $parameter" ]);
                    }
                }
            }
        }
    }
    
    /**
     * Validate checksum
     *
     * @return void
     */
    public function validateChecksum()
    {
		if (! empty($this->event_data ['event'] ['type']) && in_array($this->event_data ['event'] ['type'], ['AFFILIATE_CREATION', 'AFFILIATE_ACTIVATION'])) {
			$token_string = $this->event_data ['event'] ['type'] . $this->event_data ['merchant'] ['vendor'];
			
			if (isset($this->event_data ['affiliate'] ['vendor'])) {
				$token_string .= $this->event_data ['affiliate'] ['vendor'];
			}
		} else {
			$token_string = $this->event_data ['event'] ['tid'] . $this->event_data ['event'] ['type'] . $this->event_data ['result'] ['status'];
		}
		
        if (isset($this->event_data ['transaction'] ['amount'])) {
            $token_string .= $this->event_data ['transaction'] ['amount'];
        }
        if (isset($this->event_data ['transaction'] ['currency'])) {
            $token_string .= $this->event_data ['transaction'] ['currency'];
        }
        if (! empty($this->payment_access_key)) {
            $token_string .= strrev($this->payment_access_key);
        }

        $generated_checksum = hash('sha256', $token_string);

        if ($generated_checksum !== $this->event_data ['event'] ['checksum']) {
            $this->displayMessage([ 'message' => 'While notifying some data has been changed. The hash check failed' ]);
        }
    }
    
    /**
     * Get order reference.
     *
     * @return array
     */
    public function getOrderReference()
    {
        $order_reference           = [];
        
        // Get the transaction/order details based on the $this->tid value
        return $order_reference;
    }

    /**
     * Print the Webhook messages.
     *
     * @param array $data
     *
     * @return void
     */
    public function displayMessage($data)
    {
        print(json_encode($data));
        exit;
    }

    /**
     * Get the valid IP address.
     *
     * @return ADDR|bool
     */
    public function getIpAddress()
    {
        $ip_keys = [ 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR' ];
        foreach ($ip_keys as $key) {
            if (array_key_exists($key, $_SERVER) === true) {
                foreach (explode(',', $_SERVER[$key]) as $ip) {
                    // trim for safety measures
                    $ip = trim($ip);
                    
                    // Ensures an ip address is both a valid IP and does not fall withina private network range.
                    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === true) {
                        return $ip;
                    }
                }
            }
        }
        return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : false;
    }
}

new NovalnetWebhooks();

?>