> You are reading the Docusign Developer Documentation. For clean Markdown of any page, append .md to the page URL.
>
> Documentation indexes by section:
> - APIs and SDKs: https://docusign.docs.buildwithfern.com/docs/llms.txt
> - Platform 101: https://docusign.docs.buildwithfern.com/platform/llms.txt
> - Extension Apps: https://docusign.docs.buildwithfern.com/extension-apps/llms.txt
> - Docusign CLI: https://docusign.docs.buildwithfern.com/docusign-cli/llms.txt
>
> Authentication (OAuth, JWT Grant, M2M): https://docusign.docs.buildwithfern.com/platform/auth
> Webhooks and Connect: https://docusign.docs.buildwithfern.com/platform/webhooks
>
> OpenAPI specifications: https://docusign.docs.buildwithfern.com/openapi.json
> MCP server: https://docusign.docs.buildwithfern.com/_mcp/server

# How to validate an HMAC signature

> Describes how to implement HMAC security with Docusign Connect.

This topic demonstrates an example workflow for a client app receiving and validating a webhook (Docusign Connect) message using [HMAC security](/platform/webhooks/connect/hmac).

## Prerequisites

* The account administrator must have Docusign Connect (webhooks) enabled in your Docusign account. See [Enable Connect](https://support.docusign.com/s/document-item?bundleId=pik1583277475390\&topicId=omo1583277385804.html) for details.
* In your Docusign account, you must have [created a custom Connect configuration](/platform/webhooks/connect/create-configuration-trigger-event#create-a-custom-connect-configuration-for-testing) and [added an HMAC key](/platform/webhooks/connect/setting-up-hmac).
* You must have also [triggered and examined a test event](/platform/webhooks/connect/create-configuration-trigger-event#trigger-and-examine-a-test-event) (sent an envelope).

> **Note**
>
> Run this code example from our [Quickstart](/docs/esign-rest-api/quickstart) or launcher projects: [C#](https://github.com/docusign/code-examples-csharp), [Java](https://github.com/docusign/code-examples-java), [Node.js](https://github.com/docusign/code-examples-node), [PHP](https://github.com/docusign/code-examples-php), [Python](https://github.com/docusign/code-examples-python), or [Ruby](https://github.com/docusign/code-examples-ruby).

## Compute the HMAC hash

1. Extract the text of the plain text JSON payload as an array of bytes. The entire body of the POST request is used, including line endings.
2. Compute a SHA256 HMAC hash for the array of bytes.
3. Base64-encode the hash.

> **Note**
>
> View the source code for this how-to on GitHub: [C#](https://github.com/docusign/code-examples-csharp/blob/master/launcher-csharp/Connect/Controllers/ValidateWebhookMessage.cs), [Java](https://github.com/docusign/code-examples-java/blob/master/src/main/java/com/docusign/controller/connect/services/ValidateUsingHmacService.java), [Node.js](https://github.com/docusign/code-examples-node/blob/master/lib/connect/examples/validateWebhookMessage.js), [PHP](https://github.com/docusign/code-examples-php/blob/master/src/Services/Examples/Connect/ValidateUsingHmacService.php), [Python](https://github.com/docusign/code-examples-python/blob/master/app/connect/examples/eg001_validate_webhook_message.py), or [Ruby](https://github.com/docusign/code-examples-ruby/blob/master/app/services/connect/eg001_validate_webhook_message_service.rb).

**`C#`**

```csharp title="C#"
public static string ComputeHash(string secret, string payload)
{
    byte[] bytes = Encoding.UTF8.GetBytes(secret);
    var hmac = new HMACSHA256(bytes);
    bytes = Encoding.UTF8.GetBytes(payload);

    return Convert.ToBase64String(hmac.ComputeHash(bytes));
}

public static bool HashIsValid(string secret, string payload, string verify)
{
    ReadOnlySpan<byte> hashBytes = Convert.FromBase64String(ComputeHash(secret, payload));
    ReadOnlySpan<byte> verifyBytes = Convert.FromBase64String(verify);

    return CryptographicOperations.FixedTimeEquals(hashBytes, verifyBytes);
}
```

**`Java`**

```java title="Java"
private static String ComputeHash(String secret, byte[] payload)
        throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
    String digest = "HmacSHA256";
    Mac mac = Mac.getInstance(digest);
    mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), digest));
    return Base64.getEncoder().encodeToString(mac.doFinal(payload));
}

public static boolean HashIsValid(String secret, byte[] payload, String verify)
        throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
    String computedHash = ComputeHash(secret, payload);
    boolean isEqual = MessageDigest.isEqual(computedHash.getBytes(StandardCharsets.UTF_8),
            verify.getBytes(StandardCharsets.UTF_8));
    return isEqual;
}
```

**`Node.js`**

```javascript title="Node.js"
const computeHash = (args) => {
  const hmac = crypto.createHmac('sha256', args.secret);
  hmac.write(args.payload);
  hmac.end();
  return hmac.read().toString('base64');
};

const isHashValid = (args) => {
  return crypto.timingSafeEqual(Buffer.from(args.verify, 'base64'), Buffer.from(computeHash(args), 'base64'));
};
```

**`PHP`**

```php title="PHP"
private static function computeHash($secret, $payload)
{
    $hexHash = hash_hmac('sha256', $payload, utf8_encode($secret));
    $base64Hash = base64_encode(hex2bin($hexHash));
    return $base64Hash;
}
public static function hashIsValid($secret, $payload, $verify)
{
    return hash_equals($verify, self::computeHash($secret, $payload));
}
```

**`Python`**

```python title="Python"
key = bytes(args['secret'], 'utf-8')
payload = bytes(args['payload'], 'utf-8')

hmac_hash = hmac.new(key, payload, hashlib.sha256)
result = base64.b64encode(hmac_hash.digest()).decode('utf-8')
```

**`Ruby`**

```ruby title="Ruby"
def worker
  digest = OpenSSL::Digest.new('sha256')
  hashBytes = OpenSSL::HMAC.digest(digest, args[:secret], args[:payload])
  Base64.encode64(hashBytes)
end

def hash_valid?
  hash = worker(args[:secret], args[:payload])
  OpenSSL.secure_compare(hash.chomp, args[:signature])
end
```

## Expected response

If you run this code example from our Quickstart or launcher projects, you should see a base64-encoded HMAC hash.

Compare the base64 hash to the values of the x-docusign-signature header. There will be one x-docusign-signature header value for each HMAC key configured for your account: the first signature is in header `x-docusign-signature-1`, the second in `x-docusign-signature-2`, etc.

Compare the one you computed using your application’s HMAC key(s). At least one of the computed hashes must exactly match its corresponding header value. If there is no match, then the notification may be compromised and it should not be trusted. Only one match is required.

If you experience errors:

* Make sure that the payload is correct as a `string`. You'll need to use the correct string encoding to read the Connect payload from bytes sent on the network wire.
* If the secret has any `"` characters, remove them before the computation.

![webhook.site page annotated HMAC signature](/_fern-files/_img/b23bbbe9fdfdd4bbcb820961f34cdd6fb97cd8a298f9b74f83982f55828d062b.webp)

## Next steps

* Run this code example from our [Quickstart](/docs/esign-rest-api/quickstart) or launcher projects: [C#](https://github.com/docusign/code-examples-csharp), [Java](https://github.com/docusign/code-examples-java), [Node.js](https://github.com/docusign/code-examples-node), [PHP](https://github.com/docusign/code-examples-php), [Python](https://github.com/docusign/code-examples-python), or [Ruby](https://github.com/docusign/code-examples-ruby).
* Learn more about [Docusign Connect](/platform/webhooks/connect).
* Learn how to  [add](/platform/webhooks/connect/setting-up-hmac#add-hmac-keys-for-your-app) and [remove](/platform/webhooks/connect/setting-up-hmac#remove-hmac-keys) HMAC keys from your Docusign Connect configuration.
* See [Using HMAC Security with Docusign Connect](/platform/webhooks/connect/hmac).