Webhooks can be observed to come from LinkMoney web and IP addresses, but sometimes this is not enough to to verify that the webhook's payload is secure. To work around this, LinkMoney uses a Webhook-Verification header.

Note that the verification step is optional; LinkMoney webhooks can be used without the verification step.

<aside> ℹ️ LinkMoney does not recommend building authentication yourself. For this walkthrough, we are using the express-jwt package provided and maintained by Auth0.

</aside>

Walkthrough

1. Extract the Webhook Verification Header.

const jwt = require("express-jwt");

const { headers } = request;
const verificationHeader = request['Webhook-Verification'];
// yields JWT
const JWT = jwt.decode(verificationHeader);

2. Validate the Decoded JWT (header) fields.

{
  "alg": "ES256",
  "kid": "bfbd5111-8e33-4643-8ced-b2e642a72f3c",
  "typ": "JWT"
}

3. Use the kid field to request a webhook verification key.

const { kid } = JWT; // see step one for getting JWT
const response = await request("/webhook__verification_key", {
	method: "POST",
	headers: {
		"content-type": "application/json",
	},
	body: {
		"client_id": {MY_CLIENT_ID},
		"client_secret": {MY_CLIENT_SECRET),
		"key_id": kid, // add the kid here. 
	},
});

if (response.ok) {
	const data = await response.json(); 
	const { key } = data; 
	return key;
} else {
	// error 
}

This call will yield a JSON payload containing a single property, key. This key is a JWK.

{
  "key": {
    "alg": "ES256",
    "created_at": 1560466150,
    "crv": "P-256",
    "expired_at": null,
    "kid": "bfbd5111-8e33-4643-8ced-b2e642a72f3c",
    "kty": "EC",
    "use": "sig",
    "x": "hKXLGIjWvCBv-cP5euCTxl8g9GLG9zHo_3pO5NN1DwQ",
    "y": "shhexqPB7YffGn6fR6h2UhTSuCtPmfzQJ6ENVIoO4Ys"
  },
  "request_id": "RZ6Omi1bzzwDaLo"
}

If the request does not return a key, reject the webhook.

4. Use the JWK to validate the webhook signature.

const client = new JwksClient(options)
const key; // see above for deriving the JWK
const jwtCheck = expressJwt({
	secret: key,
	// audience
	// issuer
	algorithms: ["ES256"],
});

module.exports = jwtCheck;

If the call's signature is invalid, it will be rejected by the middleware.

5. Call the JWT Check as Middleware.

app.post("/my-api/webhook", jwtCheck, // my middlewares);