---
title: "Webhooks"
description: "Discover best practices, tools, and examples to help developers understand and use your APIs with confidence."
url: "https://developers.perryweather.com/guides/webhooks"
image: "https://developers.perryweather.com/_og/d/c_Ocean.takumi,title_Webhooks,description_~RGlzY292ZXIgYmVzdCBwcmFjdGljZXMsIHRvb2xzLCBhbmQgZXhhbXBsZXMgdG8gaGVscCBkZXZlbG9wZXJzIHVuZGVyc3RhbmQgYW5kIHVzZSB5b3VyIEFQSXMgd2l0aCBjb25maWRlbmNlLg,props_eyJ0aGVtZSI6eyJtb2RlIjoiZGFyayIsImNvbG9ycyI6eyJwcmltYXJ5IjoiIzAwODE5RSJ9fX0,p_Ii9ndWlkZXMvd2ViaG9va3Mi,s_N0o7dEIci65jd4-T.png"
---

Guides

## Webhooks

Our webhooks deliver weather alerts for locations within your Perry Weather account to any destination you specify. Your platform can then distribute those events as you see fit.

## [Webhook Data Model](#webhook-data-model)

See the [API reference](https://developers.perryweather.com/apis/perry-weather-webhooks-1/versions) for the complete request and event data models.

## [Webhook Validation](#webhook-validation)

If your webhook exposes sensitive parts of your platform you might want to verify requests are coming from Perry Weather and not a malicious third party. Perry Weather cryptographically signs every webhook payload using HMAC-SHA256 with a **signing secret** unique to your account.

### [Retrieving Your Signing Secret](#retrieving-your-signing-secret)

Signing secrets are managed in the Perry Weather web app, similar to API keys.

1.  In the Perry Weather dashboard, navigate to [**Integrations > Perry Weather APIs**](https://app.perryweather.com/Integrations/ApiManagement).
2.  Select the webhooks tab.
3.  Click the **Signing Secret** button to get the key.

Important: Treat the signing secret with the same care as an API key. Store it securely (environment variables or a secrets manager) and never expose it in client-side code or version control. If compromised, contact `support@perryweather.com` to rotate it.

### [How Perry Weather Signs Webhooks - v1.2+](#how-perry-weather-signs-webhooks-v12)

1.  Perry Weather prepares the full JSON body of the webhook payload exactly as it will be sent.
2.  We compute an HMAC-SHA256 signature of that entire body using your signing secret as the key.
3.  The signature (Base64-encoded) is attached to the request in the `x-pw-signature` header.

Note: This signing method (signing the entire webhook body) is used starting with `version 1.2` of Perry Weather webhooks. Previous versions (`1.0 and 1.1`) use the legacy method of signing only the URL with metadata. See this page for details on the [legacy method.](https://developers.perryweather.com/guides/webhooks/webhooks-legacy).

### [Validating Requests in Your Application](#validating-requests-in-your-application)

To verify a webhook came from Perry Weather:

1.  Extract the raw request body as a string/byte array (do not parse it first, as whitespace or encoding differences can invalidate the signature).
2.  Compute the HMAC-SHA256 hash of that body using your signing secret.
3.  Base64-encode the resulting hash.
4.  Compare it (constant-time comparison) to the value in the `x-pw-signature` header.

If the signatures match, the request is authentic and untampered. If they do not match or the header is missing, discard the request.

### [Example implementations (adapt to your language/framework):](#example-implementations-adapt-to-your-languageframework)

**Node.js / Express:**

```Javascript
const crypto = require('crypto');

function validateWebhook(req, res, next) {
  const signature = req.headers['x-pw-signature'];
  if (!signature) return res.status(401).send('Missing signature');

  const body = req.rawBody || JSON.stringify(req.body); // Use raw body
  const hmac = crypto.createHmac('sha256', YOUR_SIGNING_SECRET);
  const digest = hmac.update(body).digest('base64');

  if (crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature))) {
    next();
  } else {
    res.status(401).send('Invalid signature');
  }
}
```

**Python:**

```Python
const crypto = require('crypto');

function validateWebhook(req, res, next) {
  const signature = req.headers['x-pw-signature'];
  if (!signature) return res.status(401).send('Missing signature');

  const body = req.rawBody || JSON.stringify(req.body); // Use raw body
  const hmac = crypto.createHmac('sha256', YOUR_SIGNING_SECRET);
  const digest = hmac.update(body).digest('base64');

  if (crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature))) {
    next();
  } else {
    res.status(401).send('Invalid signature');
  }
}
```

**C# (.NET):**

```C#
using System.Security.Cryptography;
using System.Text;

public bool ValidateSignature(string body, string receivedSignature, string secret)
{
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(body));
    string computed = Convert.ToBase64String(hash);

    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(computed),
        Encoding.UTF8.GetBytes(receivedSignature));
}
```

### [Best Practices](#best-practices)

-   Always validate the signature before processing any webhook data.
-   Use raw request body for hashing to avoid serialization differences.

For questions about webhooks, versioning, or your signing secret, contact [support@perryweather.com](mailto:support@perryweather.com).