> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gr4vy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook signatures

Webhook signatures can be used to verify the authenticity and integrity of a received webhook notification.
By verifying the webhook signature merchants can protect themselves from malicious parties sending tampered webhook
data to the merchant's webhook endpoint.

## Configuration

When configuring a [webhook subscription](./overview#setup), a secret can be generated that is used
to sign the webhooks content. Please head over to the **Webhook subscriptions** dashboard, select the subscription,
select the actions menu, and select **Generate secret**. Once created, the secret can be copied from the table and used
to verify webhooks.

<Note>Please note it may take 30 seconds for the signature to appear in webhook notifications.</Note>

## Signature verification

When a secret is enabled for a webhook, the following HTTP headers are sent with every webhook message.

* `X-Gr4vy-Webhook-Timestamp`: UNIX timestamp in seconds used to generate the signature.
* `X-Gr4vy-Webhook-Signatures`: comma-separated list of signatures for each of the active secrets.
* `X-Gr4vy-Webhook-ID`: unique reference to webhook across retries which acts as the idempotency value.

In order to verify the webhook content the following steps must be followed.

1. Append the timestamp header and payload contents (`{timestamp}.{payload}`).
2. Compute the HMAC SHA256 value using the webhook subscription secret.
3. Make sure the computed value matches at least one of the signature header values.
4. (Optional) Check that timestamp is not too old to prevent replay attacks.

### SDK support

The SDKs have been updated with support for verifying webhook signatures. The following code snippets show how to verify the webhook signature using these help methods.

<CodeGroup>
  ```csharp C# theme={"system"}
  using Gr4vy;
  using Gr4vy.Models.Components;
  using System.Collections.Generic;

  // Loaded the key from a file, env variable, 
  // or anywhere else
  var privateKey = "YOUR_PRIVATE_KEY"; 

  var sdk = new Gr4vySDK(
      id: "example",
      server: SDKConfig.Server.Sandbox,
      bearerAuthSource: Auth.WithToken(privateKey),
      merchantAccountId: "default"
  );
  ```

  ```go Go theme={"system"}
  using Gr4vy;

  // Webhook payload and headers
  string payload = "your-webhook-payload";
  string secret = "your-webhook-secret";
  string signatureHeader = "signatures-from-header";
  string timestampHeader = "timestamp-from-header";
  int timestampTolerance = 300; // optional, in seconds (default: 0)

  try {
      Webhooks.Verify(Payload, Secret, signatureHeader, timestampHeader, timestampTolerance);
      
  }
  catch(ArgumentException ex) {
      // handle the exception
  }
  ```

  ```java Java theme={"system"}
  import com.gr4vy.sdk.Webhooks;

  String payload = 'your-webhook-payload';
  String secret = 'your-webhook-secret';
  String signatureHeader = 'signatures-from-header';
  String timestampHeader = 'timestamp-from-header';
  String timestampTolerance = 300; // optional, in seconds (default: 0)

  try {
      Webhooks.verifyWebhook(payload, secret, wrongSignatureHeader, timestampTolerance, timestampHeader);
      System.out.println("Webhook verified successfully.");
  } catch (IllegalArgumentException e) {
      System.err.println("Invalid input: " + e.getMessage());
  }
  ```

  ```php PHP theme={"system"}
  use Gr4vy;

  // Webhook payload and headers
  $payload = "your-webhook-payload";
  $secret = "your-webhook-secret";
  $signatureHeader = "signatures-from-header";
  $timestampHeader = "timestamp-from-header";
  $timestampTolerance = 300; // optional, in seconds (default: 0)

  try {
      Webhooks::verifyWebhook(
          secret: $secret$,
          payload: $payload,
          signatureHeader: $signatureHeader$,
          timestampHeader: $timestampHeader,
          timestampTolerance: $timestampTolerance
      );
  }
  catch(Throwable $th$) {
      // handle the exception
  }
  ```

  ```python Python theme={"system"}
  from gr4vy.webhooks import verify_webhook

  # Webhook payload and headers
  payload = 'your-webhook-payload'
  secret = 'your-webhook-secret'
  signature_header = 'signatures-from-header'
  timestamp_header = 'timestamp-from-header'
  timestamp_tolerance = 300  # optional, in seconds (default: 0)

  try:
      # Verify the webhook
      verify_webhook(
          payload=payload,
          secret=secret,
          signature_header=signature_header,
          timestamp_header=timestamp_header,
          timestamp_tolerance=timestamp_tolerance
      )
      print('Webhook verified successfully!')
  except ValueError as error:
      print(f'Webhook verification failed: {error}')
  ```

  ```ts TypeScript theme={"system"}
  import { verifyWebhook } from "@gr4vy/sdk";

  const payload = 'your-webhook-payload'
  const secret = 'your-webhook-secret'
  const signatureHeader = 'signatures-from-header'
  const timestampHeader = 'timestamp-from-header'
  const timestampTolerance = 300 // optional, in seconds (default: 0)

  try {
    verifyWebhook(
      payload,
      secret,
      signatureHeader,
      timestampHeader,
      timestampTolerance
    )
    console.log('Webhook verified successfully!')
  } catch (error) {
    console.error('Webhook verification failed:', error.message)
  }
  ```
</CodeGroup>

<Note>
  Bear in mind that extracting the payload value must include all the formatting as it is received. For example, if the payload is a JSON object, it must be formatted as a string
  with all the spaces and line breaks. If using a JSON library to parse the payload, make sure to use the original value received.
</Note>

## Secret rotation

In the webhook subscriptions page the secret can also be rotated. Secret rotation allows you to safely change the secret
used to sign webhooks. It is possible to set a custom rotation period during which the system sends signatures for both
the old secret and the new one. After this period, only the new secret is used to sign webhooks.

To rotate a secret, please following these steps.

1. Generate a new secret using the **Rotate secret** menu in the webhook subscriptions page.
2. Update your webhook endpoint to use the new secret.
3. Verify the webhooks are processed using the new secret.
4. Wait for the old secret to expire after the rotation period.

<Note>Please note it may take 30 seconds for the new signatures to appear in webhook notifications.</Note>

## Idempotency

The `X-Gr4vy-Webhook-ID` header is a unique reference to the webhook across any retries. For example,
you may successfully process a webhook and return response but due to network problems the system did not
receive the response. The request is then retried and your code must know that the webhook
was already processed.

If the webhook handling is idempotent itself this might not be a real problem but still a waste of
resources. You can store the latest webhooks processed extracting the id from `X-Gr4vy-Webhook-ID`
to avoid processing them again.
