---
id: receiving
title: "Receiving webhooks"
sidebar_label: "Receiving webhooks"
hide_title: true
description: "Endpoint requirements, request structure, retries, headers and HMAC verification for Walley webhooks"
---

import Ingress from "../../src/components/ingress/ingress.react";
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

<Ingress title="Receiving webhooks">
  Your system receives webhooks on an HTTPS endpoint that you register in
  Merchant Hub. This page covers what the endpoint must support, what the
  requests look like, how retries work, and how to verify that a request
  comes from Walley.
</Ingress>

## Expose an HTTPS endpoint

To receive webhooks you are required to have an HTTPS endpoint.

**Endpoint requirements**

- Must accept HTTP POST requests
- Must accept a JSON request body
- Must support HTTPS with TLS 1.2 or 1.3

**Endpoint recommendations**

- Require basic authentication with a username and password
- Support HTTP2
- Validate the Walley-Signature header with the HMAC key

## Respond to webhooks

When you receive a webhook request you are required to respond with an HTTP status code in the 2xx series.

## Structure

The request body is formatted as JSON. `Type` holds the name of the event and `Timestamp` is when the event occurred. `Payload` is different for each event.

```json
{
    "Type": string,
    "Timestamp": date,
    "Payload": object
}
```

## Retries

Webhooks are delivered at least once, so your endpoint must support being called multiple times with the same event without side effects. We retry delivery with increasing backoff time until we receive an HTTP status code in the 2xx series.

The webhook is sent instantly, and if the endpoint does not answer with a 2xx status, a retry will be made after:

- 5 seconds
- 10 seconds
- 3 minutes
- 1 hour
- 4 hours
- 8 hours
- 16 hours
- 24 hours

If the webhook endpoint did not answer with a 2xx status after the last retry (after about 53 hours), we will handle this manually and contact you before retrying again.

## Headers

These headers are included in every webhook request:

| Key                  | Description                                                                                                                                                                        |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| User-Agent           | Standard HTTP header. The value will be something like `Walleybot 0.1 (+https://dev.walleypay.com)`.                                                                               |
| Walley-CorrelationId | The id of the request that triggered the webhook request. If you have issues with any request, provide this value in the support ticket.                                           |
| Walley-Timestamp     | UNIX timestamp when this event occurred. Used for HMAC validation, see [Verifying the request with HMAC](#verifying-the-request-with-hmac).                                        |
| Walley-Signature     | Computed signature from Walley. Used for HMAC validation, see [Verifying the request with HMAC](#verifying-the-request-with-hmac).                                                 |
| Walley-Live          | `True` or `False`. Used to know if the request to the webhook is made from Walley's production or a test triggered from the Merchant Hub. `True` indicates production data.        |

## Verifying the request with HMAC

When you set up a webhook, you'll get an HMAC key for it. You can use this key to verify that the requests you receive originate from Walley, using the following method:

1. Retrieve the request header `Walley-Timestamp`
2. Concatenate the version number, the timestamp, and the body of the request to form a basestring. Use a semicolon as the delimiter between the three elements. The version number right now is always `v0`. Example: `v0;{Timestamp};{Body}`
3. Hash the string described above with the HMAC SHA256 implementation of your system's platform, using the HMAC secret for the webhook.
4. Compare the computed signature with the request header `Walley-Signature`

### Examples

<Tabs defaultValue="csharp" values={[
{ label: 'C#', value: 'csharp', },
{ label: 'Node.js', value: 'nodejs', },
{ label: 'PHP', value: 'php', },
]}>

<TabItem value="csharp">
```cs
static string ComputeHash(string secret, string timestamp, string payload)
{
    byte[] bytes = Encoding.UTF8.GetBytes(secret);
    HMACSHA256 hmac = new HMACSHA256(bytes);
    bytes = Encoding.UTF8.GetBytes($"v0;{timestamp};{payload}");

    return Convert.ToHexString(hmac.ComputeHash(bytes)).ToLower();

}

static bool IsHashValid(string secret, string timestamp, string payload, string verify)
{
string hash = ComputeHash(secret, timestamp, payload);
ReadOnlySpan<byte> hashBytes = Convert.FromHexString(hash);
ReadOnlySpan<byte> verifyBytes = Convert.FromHexString(verify);

    return CryptographicOperations.FixedTimeEquals(hashBytes, verifyBytes);

}

```
</TabItem>

<TabItem value="nodejs">
```js
const crypto = require('crypto');

const computeHash = (secret, timestamp, payload) => {
	const hmac = crypto.createHmac('sha256', secret);
	hmac.write(`v0;${timestamp};${payload}`);
	hmac.end();
	return hmac.read().toString('hex');
};

const isHashValid = (secret, timestamp, payload, verify) => {
	const computed = Buffer.from(computeHash(secret, timestamp, payload), 'hex');
	const received = Buffer.from(verify, 'hex');

	return received.length === computed.length && crypto.timingSafeEqual(received, computed);
};
```
</TabItem>
<TabItem value="php">
```php
function computeHash($secret, $timestamp, $payload) {
    $hmac = hash_hmac('sha256', "v0;$timestamp;$payload", $secret, true);

    return bin2hex($hmac);
}

function isHashValid($secret, $timestamp, $payload, $verify) {
    $hash = computeHash($secret, $timestamp, $payload);

    return hash_equals($hash, strtolower($verify));
}
```

</TabItem>
</Tabs>
