Blog
Home/

HMAC verification with PHP

Ivan Dinkov
Ivan DinkovSr. Developer Support Advisory Engineer
Summary3 min read

Learn how to secure your Docusign Connect webhook calls with HMAC verification

    • The problem: Testing your HMAC verification code
    • Example: Checking the HMAC signature using vanilla PHP
    • Summary
    • Additional resources

    Table of contents

    To secure your Connect webhook notification messages, Docusign recommends using the HMAC feature. With HMAC, your message is cryptographically hashed using a private key known only to you and Docusign. It’s set at the account level. The resulting hash key is included in the header of the notification message. When the message is received, your application repeats the hash operation and compares the result to the HMAC signature included in the header.  

    HMAC guarantees that the message is from Docusign and has not been altered on the way by a third party.

    The problem: Testing your HMAC verification code

    Regardless of your listener implementation, whether Platform-as-a-Service (PaaS) as suggested in Building best practices webhook listeners, part 4 or a custom listener, you face the issue of constantly resending envelopes while you test your application.

    The PHP sample code below verifies the HMAC signature sent in the header of the notification message**.** To test the code on your localhost development machine, use the Postman replaying webhook messages technique.

    Note that the HMAC hash should be verified before the request body is parsed. The request body is untrusted until the HMAC hash is verified. 

    Example: Checking the HMAC signature using vanilla PHP

    <?php // Copyright (c) 2020 Docusign Inc. MIT License https://opensource.org/licenses/MIT
    // save this file as index.php in your web server root folder
    // enable only in development
    error_reporting(E_ALL);
    ini_set('display_errors', 1); 
    
    $secret = "your secret key";// your secret key
    $payload = "";// request body
    $headers = "";// request message headers array
    
    $signature = "";// the HMAC hash key in the HTTP header x-docusign-signature-1
    $result = false;// verification result
    
    if (isset($_POST)) {
        try {
            $payload = file_get_contents('php://input');
            $headers = get_ds_headers();
            if (array_key_exists("XDocusignSignature1", $headers)) {
                $signature = $headers["XDocusignSignature1"];
                $result = hash_is_valid($secret, $payload, $signature);
                log_result($signature, $payload, $result);
            }
         } catch (Exception $e) {
            logger("\nException: " . $e->getMessage() . "\n");
        }
        header("HTTP/1.1 200 OK");
    }
     
    function get_ds_headers()
    {
        $headers = array();
        foreach ($_SERVER as $key => $value) {
            if (strpos($key, 'HTTP_') === 0) {
                $headers[str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))))] = $value;
            }
        }
        return $headers;
    }
     
    function compute_hash($secret, $payload)
    {
        $hexHash = hash_hmac('sha256', $payload, utf8_encode($secret));
        $base64Hash = base64_encode(hex2bin($hexHash));
        return $base64Hash;
    }
     
    function hash_is_valid($secret, $payload, $verify)
    {
        $computed_hash = compute_hash($secret, $payload);
        eturn hash_equals($verify,$computed_hash);
    }
     
    function log_result($signature, $payload, $result)
    {
        $result_to_log = $result == 1 ? "pass" : "fail";
        if ($result) {
            try {
                $xml = simplexml_load_string($payload);
                $envelopeId = $xml->EnvelopeStatus->EnvelopeID;
                $status = $xml->EnvelopeStatus->Status;
                $created = $xml->EnvelopeStatus->Created;
            } catch (Exception $e) {
                logger("\nException: " . $e->getMessage() . "\n");
            }
            logger("\n");
            logger("EnvelopeID: " . $envelopeId);
            logger("Signature: " . $signature);
            logger("HMAC check status: " . $result_to_log);
            logger("Envelope Status: " . $status);
            logger("Generated at: " . $created);
        } else {
            logger("\n");
            logger("HMAC check status: " . $result_to_log);
            logger("Generated at: " . date('Y-m-d H:i:s'));
        }
    }
    
    function logger($txt)
    {
        $log_file = "log.txt";
        $myfile = fopen($log_file, "a") or die("Unable to open file!");
        fwrite($myfile, "\n" . $txt);
        fclose($myfile);
    }
     ?>
    

    Summary

    By using the Postman replay technique, you can test your HMAC verification implementation in your local environment.

    Additional resources

    Ivan Dinkov
    Ivan DinkovSr. Developer Support Advisory Engineer
    More posts from this author

    Related posts

    • Developers

      Introducing OAuth for Connect: enhanced security for webhooks

      Alan Roza
      Alan Roza
    • Updated Docusign University Learning Portal for Developers

      Sasha Vodnik
      Sasha Vodnik
    • Recipient Connect

      Larry Kluger
      Larry Kluger

    Introducing OAuth for Connect: enhanced security for webhooks

    Alan Roza
    Alan Roza

    Updated Docusign University Learning Portal for Developers

    Sasha Vodnik
    Sasha Vodnik

    Recipient Connect

    Larry Kluger
    Larry Kluger

    Discover what's new with Docusign IAM or start with eSignature for free

    Explore Docusign IAMTry eSignature for Free
    Person smiling while presenting