---
title: Signature Verification
slug: developer-tools/webhooks/signature-verification
excerpt: Learn how to ensure the webhook payload received is sent by Pine Labs Online.
hidden: false
sidebar_order: 3
metadata:
  title: Webhook Signature Verification — Secure Event Validation | Pine Labs
  description: >-
    Verify webhook signatures using HMAC SHA-256 to ensure authenticity and
    security of Pine Labs payment event notifications. Step-by-step guide with
    code samples for signature validation.
  image: []
  keywords: >-
    webhook signature, signature verification, HMAC SHA-256, webhook security,
    event validation, webhook authentication
  robots: index
---

Webhook signature verification is a critical security measure to protect your system from unauthorized access, data tampering, and other potential threats.

We include a signature that you can use to verify that the webhook payload you receive is legitimate and sent by us. The signature is sent against the `webhook-signature` parameter in the header in all the webhook events.

## Verify Signature

Follow the below steps to verify the webhook signature.

1. All the webhook events sent from Pine Labs Online include these three header information along with the body which are used for verification.
   1. `webhook-id`: A unique identifier of the webhook event. This remains the same when the webhook is resent and this can be ignored.
   2. `webhook-timestamp`: The unix timestamp (in seconds) when the webhook event was triggered.
   3. `webhook-signature`: Base64 encoded webhook signature.
2. Use the `webhook_id`, `webhook_timestamp`, `body`, and `secret_key` to generate a signature.

> 📘 Note
> 
> - Where `body` is the raw response body of a webhook event. Ensure that the webhook body remains unparsed.
> - `secret_key`: You can find your secret key under settings on the <a href="https://dashboardv2.pluralonline.com/login" target="_blank">Pine Labs Online dashboard</a>.
> - Implementing webhook signature verification is mandatory.

3. You can use the sample code below to generate a signature and encode it to Base64.


<CodeTabs>
  <Tab label="Java">
```java
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class Main {
  public static void main(String[] args) {
    String webhook_id = "<webhook_id>"; // Replace with actual webhook_id
    String webhook_timestamp = "<webhook_timestamp>"; // Replace with actual webhook_timestamp
    String body = "<body>"; // Replace with actual body

    String signedContent = webhook_id + "." + webhook_timestamp + "." + body;
    String secret = "<secret_key>"; // Replace with actual secret key encoded to base64.

    // Need to base64 decode the secret
    byte[] secretBytes = Base64.getDecoder().decode(secret);
    String signature = "";

    try {
      Mac mac = Mac.getInstance("HmacSHA256");
      SecretKeySpec secretKeySpec = new SecretKeySpec(secretBytes, "HmacSHA256");
      mac.init(secretKeySpec);
      byte[] hmacBytes = mac.doFinal(signedContent.getBytes(StandardCharsets.UTF_8));
      signature = Base64.getEncoder().encodeToString(hmacBytes);
    } catch (Exception e) {
      e.printStackTrace();
    }

    System.out.println(signature);
  }
}
```
  </Tab>
  <Tab label="Kotlin">
```kotlin
import java.nio.charset.StandardCharsets
import java.util.Base64
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec

fun main() {
    val webhookId = "msg_2nEfCaUDn9fynC9Kz2upo1QSydl"
    val webhookTimestamp = "1728543028"
    val body = "{\"payload\":\"payload\"}"

    val signedContent = "$webhookId.$webhookTimestamp.$body"
    val secret = "YWJjMTIzNA=="

    // Base64 decode the secret
    val secretBytes = Base64.getDecoder().decode(secret)
    var signature = ""

    try {
        val mac = Mac.getInstance("HmacSHA256")
        val secretKeySpec = SecretKeySpec(secretBytes, "HmacSHA256")
        mac.init(secretKeySpec)
        val hmacBytes = mac.doFinal(signedContent.toByteArray(StandardCharsets.UTF_8))
        signature = Base64.getEncoder().encodeToString(hmacBytes)
    } catch (e: Exception) {
        e.printStackTrace()
    }

    println(signature)
}
```
  </Tab>
  <Tab label="Python">
```python
import hmac
import hashlib
import base64

webhook_id = "<webhook_id>"
webhook_timestamp = "<webhook_timestamp>"
body = "<body>"

signed_content = f"{webhook_id}.{webhook_timestamp}.{body}"
secret = "<secret_key>"  # Base64-encoded secret

secret_bytes = base64.b64decode(secret)
signature = base64.b64encode(
    hmac.new(secret_bytes, signed_content.encode("utf-8"), hashlib.sha256).digest()
).decode("utf-8")

print(signature)
```
  </Tab>
  <Tab label="C#">
```csharp
using System;
using System.Security.Cryptography;
using System.Text;

class Program
{
    static void Main(string[] args)
    {
        string webhookId = "<webhook_id>";
        string webhookTimestamp = "<webhook_timestamp>";
        string body = "<body>";

        string signedContent = $"{webhookId}.{webhookTimestamp}.{body}";
        string secret = "<secret_key>"; // Base64-encoded secret

        byte[] secretBytes = Convert.FromBase64String(secret);

        using var hmac = new HMACSHA256(secretBytes);
        byte[] hmacBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(signedContent));
        string signature = Convert.ToBase64String(hmacBytes);

        Console.WriteLine(signature);
    }
}
```
  </Tab>
  <Tab label="Javascript">
```javascript
const crypto = require("crypto");

const webhookId = "<webhook_id>";
const webhookTimestamp = "<webhook_timestamp>";
const body = "<body>";

const signedContent = `${webhookId}.${webhookTimestamp}.${body}`;
const secret = "<secret_key>"; // Base64-encoded secret

const secretBytes = Buffer.from(secret, "base64");
const signature = crypto
  .createHmac("sha256", secretBytes)
  .update(signedContent, "utf8")
  .digest("base64");

console.log(signature);
```
  </Tab>
  <Tab label="Go">
```go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
)

func main() {
	webhookId := "<webhook_id>"
	webhookTimestamp := "<webhook_timestamp>"
	body := "<body>"

	signedContent := webhookId + "." + webhookTimestamp + "." + body
	secret := "<secret_key>" // Base64-encoded secret

	secretBytes, err := base64.StdEncoding.DecodeString(secret)
	if err != nil {
		panic(err)
	}

	mac := hmac.New(sha256.New, secretBytes)
	mac.Write([]byte(signedContent))
	signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))

	fmt.Println(signature)
}
```
  </Tab>
</CodeTabs>





4. Next, compare the generated signature with the header `webhook_signature`.
5. To compare the signatures it's recommended to use a constant-time string comparison method in order to prevent timing attacks.
6. Additionally, you can also compare the webhook timestamp against your system timestamp to ensure it's within your acceptable time range.

> 📘 Note:
> 
> By comparing the generated signature with the one provided, you can verify that the webhook was sent by Pine Labs Online and that the data remained intact during transmission.

### Example:

The example below illustrates the webhook signature verification process clearly and provides a step-by-step guide for a better understanding.

1. **Merchant Secret Key**: The merchant has a secret key, abc1234. Before using this key for signature verification, we need to encode it to Base64, which results in: `YWJjMTIzNA==`
2. **Webhook Event**: A webhook event is received with the following details:
   1. webhook-id: `msg_2nEfCaUDn9fynC9Kz2upo1QSydl`
   2. webhook-timestamp: `1728543028`
   3. webhook-signature: `v1,Ns46HrH+Nfu9dZtBUVvSLyrOD5JH0SAGlNo3M5yobfQ=`
3. **Signature Verification**: To verify the authenticity of this webhook, we need to recreate the signature using the webhook-id, webhook-timestamp, and the request body.
   1. First, combine the webhook-id, webhook-timestamp, and the body into a single string (signed content):
   2. Now, use the Base64 decoded secret (YWJjMTIzNA==) as the key to generate the HMAC SHA-256 signature over the signed content. The code provided performs this operation.

```kotlin
fun main() {
    val webhookId = "msg_2nEfCaUDn9fynC9Kz2upo1QSydl"
    val webhookTimestamp = "1728543028"
    val body = "{\"payload\":\"payload\"}"
 
    val signedContent = "$webhookId.$webhookTimestamp.$body"
    val secret = "YWJjMTIzNA=="
 
    // Base64 decode the secret
    val secretBytes = Base64.getDecoder().decode(secret)
    var signature = ""
 
    try {
        val mac = Mac.getInstance("HmacSHA256")
        val secretKeySpec = SecretKeySpec(secretBytes, "HmacSHA256")
        mac.init(secretKeySpec)
        val hmacBytes = mac.doFinal(signedContent.toByteArray(StandardCharsets.UTF_8))
        signature = Base64.getEncoder().encodeToString(hmacBytes)
    } catch (e: Exception) {
        e.printStackTrace()
    }
 
    println(signature)
}
```

The output of this code will generate the following signature: `Ns46HrH+Nfu9dZtBUVvSLyrOD5JH0SAGlNo3M5yobfQ=`

4. **Webhook Signature Validation**: The received webhook signature starts with the prefix v1, indicating the version of the signature algorithm. In this case, the v1 prefix is followed by the signature: `Ns46HrH+Nfu9dZtBUVvSLyrOD5JH0SAGlNo3M5yobfQ=` By matching the generated signature with the part of the received signature after prefix v1, we confirm that the webhook is authentic. The webhook has been securely sent from Pine Labs Online.
