Authorize.Net’s webhook server end listener using custom code for Laravel and PHP

Before reading this blog post, You might be already stretch your head, scrolled so many other blog posts, and punching your keyboard keys on the Authorize.Net developer forum.

Don’t worry! Before writing this blog post. I did similar 🙂

This blog post example fits for Laravel’s development. But in the end, it has a custom PHP code as well. It will help you to fix your custom PHP application code.

Setup Authorize.Net webhook

I am not describing it to you. What is a webhook & how it works? Because if you are reading this post. I hope you are looking for a solution and I don’t want to waste your precious time.
Anyway, Kindly read about the webhook on Authorize.Net developer API reference.

A WebHook is an HTTP callback: an HTTP POST that occurs when something happens; a simple event-notification via HTTP POST. A web application implementing WebHooks will POST a message to a URL when certain things happen.

Authorize.Net Webhook creation

Prerequisites

You must configure a Signature Key in the Authorize.Net Merchant Interface before you can receive webhook notifications. Obtain the Signature Key from the Authorize.Net Merchant Interface, at Account > Settings > Security Settings > General Security Settings > API Credentials and Keys.

Receive and Parse The Webhook Notification

In this blog post, we are developing the solutions for “net.authorize.payment.authcapture.created” event type.
You can use it for other webhook event types.
This blog post example is developing the Laravel model for webhook validation. You can convert it to middleware and use it in the controller class.

1. Model Class

Run command on command prompt.

php artisan make:model AuthnetWebhook
namespace App;

use Illuminate\Database\Eloquent\Model;
use App\Exceptions;
class AuthnetWebhook extends Model{
	
	/**
     * @var object  SimpleXML object representing the Webhook notification
    */
    private $webhook;

    /**
     * @var string  JSON string that is the Webhook notification sent by Authorize.Net
     */
    private $webhookJson;

    /**
     * @var array  HTTP headers sent with the notification
     */
    private $headers;

    /**
     * @var string  Authorize.Net Signature Key
     */
    private $signature;

	
	
	/**
     * Creates the response object with the response json returned from the API call
     *
     * @param  string $signature Authorize.Net Signature Key
     * @param  string $payload   Webhook Notification sent by Authorize.Net
     * @param  array  $headers   HTTP headers sent with Webhook. Optional if PHP is run as an Apache module
    */
    public function __construct(string $signature, string $payload, array $headers = [])
    {
		
        $this->signature   = $signature;
        $this->webhookJson = $payload;
        $this->headers     = $headers;
        if (empty($this->headers)) {
            $this->headers = $this->getAllHeaders();
        }
        
        $this->headers = array_change_key_case($this->headers, CASE_UPPER);
    }
	
	/**
     * Validates a webhook signature to determine if the webhook is valid
     *
     * @return bool
    */
    public function isValid(): bool
    {
        $hashedBody = strtoupper(hash_hmac('sha512', $this->webhookJson, $this->signature));
		return (isset($this->headers['X-ANET-SIGNATURE']) &&
            strtoupper(explode('=', $this->headers['X-ANET-SIGNATURE'])[1]) === $hashedBody);
    }
	
    /**
     * Retrieves all HTTP headers of a given request
     *
     * @return array
    */
    protected function getAllHeaders(): array {
	
        if (function_exists('apache_request_headers')) {
            $headers = apache_request_headers();
        } else {
            $headers = [];
            foreach ($_SERVER as $key => $value) {
                if (strpos($key, 'HTTP_') === 0) {
                    $headers[str_replace('_', '-', substr($key, 5))] = $value;
                }
            }
        }
        return $headers;
    }
	
}

2. Controller class

Run command on command prompt.

 php artisan make:controller AuthnetWebhookController 
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\AuthnetWebhook;
use Mail;

class AuthnetWebhookController extends Controller{
	
	public function webhookListener(Request $request){
	
		$authorize_payment_api_login_id = 'API_LOGIN_ID';
		$authorize_payment_transaction_key = 'API_Transaction_Key';
		$authorize_payment_env = 'sandbox';
		$authorize_payment_signature_key = 'API_Sign_Key';
		
		// getallheaders() is php standard function.
		$headers = getallheaders();
		$payload = $request->getContent(); // for laravel 
		
		//$payload = file_get_contents('php://input') // for non Laravel platforms
		
		// initialization of Model class
		$webhook = new AuthnetWebhook($authorize_payment_signature_key, $payload, $headers);
		
		// check the valid signature and payload data. 
		
		if ($webhook->isValid()) {
		
			// Access notification values
			$payload_json_obj = json_decode($payload);
			// Get the transaction ID
			$transactionId = $payload_json_obj->payload->id;
			
			// get authorized.net transaction details. 
			//$response = $this->getTransactionDetails($transactionId);
			
			$this->AuthWebhookTestEmail($transactionId, 'Webhook Triggered. Transaction id is not found.');
			
			http_response_code(200);
			
		} else {
		
			$this->AuthWebhookTestEmail('Empty', 'Webhook Triggered. Transaction id is not found.');
			http_response_code(200);
		}
	}
	
	public function AuthWebhookTestEmail($transaction_id, $status){
		$email_data = array (
			'to' => '[email protected]',
			'to_name' =>  'Narinder Singh', 
			'subject' => 'auth webhook triggered email',
			'from_email' => '[email protected]',
			'from_name' => '12dot6'
		);
		
		$email_data['email_content'] = 'Transaction Id: '.$transaction_id.' <br/>
		Status: '.$status.' <br/>';

		Mail::send([], $email_data, function($message)  use ($email_data) {
		
			$message->to($email_data['to'] , $email_data['to_name'])
			->subject($email_data['subject']);
			
			$message->from($email_data['from_email'] ,$email_data['from_name']);
			$message->setBody($email_data['email_content'], 'text/html');

		});
	}
}

The controller class has a code line.

$payload = $request->getContent();

Generally, most other examples, suggest this code.

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

The file_get_contents() method is PHP standard method. If you are going to create your application in another PHP platform or frameworks. You will use file_get_contents() method. For Laravel $request->getContent() is recommended.

3. Add route to routes/web.php

Keep in mind the webhook listener route must be post route. Webhook send post inputs to the listener. If your route method is GET, it will execute but webhook inputs will not access.

Route::post('authnet-webhook-listener', 'AuthnetWebhookController@webhookListener');

Core PHP code.

$headers = getallheaders();
$payload = file_get_contents('php://input');

// Access notification values
$payload_json_obj = json_decode($payload);
// Get the transaction ID
$transactionId = $payload_json_obj->payload->id;

error_log(file_get_contents('php://input'), 3, 'logfile.txt'); 
http_response_code(200);

Tips and tricks.

  • In Laravel routes must be POST method route.
  • Authorized.Net signature key must be enabled.
  • In the webhook endpoint for Laravel don’t put forward slash “/” at end of the URL.
  • file_get_content(‘php://input’) this function sometimes didn’t work on server. So kindly check your server php.ini settings. “allow_url_fopen” must be true. ini_set(“allow_url_fopen”, true);
  • Keep webhook inactive for testing.

Testing the code.

  • Test your webhook payload variable storing into error_log.
    error_log($payload, 3, ‘logfile_webhook.txt’);
  • Test your webhook using a test email. I already added the email code in the controller class.

If you are looking for a full package for Laravel. You can try this GitHub Package.