When making requests to an API, you typically expect a quick response. However, for some long-running operations, an immediate response is not always feasible, and waiting could lead to timeout errors. In such cases, the API might return a "pending" response while continuing to process your request asynchronously. To keep your records updated with the final status, you can:

  1. Poll the API: Send periodic requests to the API to get updated information.
  2. Use Webhooks: Provide a URL endpoint to which the API sends updates automatically as events occur.

Webhooks are an efficient solution for monitoring the progress of asynchronous operations such as transactions or payouts. For example, webhook events can be triggered to describe the current state of the operation. These events can be Webhook Transaction Events or Webhook Payout Events.

Validating Webhook Signatures

To ensure the authenticity of incoming webhook events, Vendy includes an X-Signature header with each event. This header contains an HMAC SHA256 signature of the event payload, signed using the secretHash you provided when setting up the webhook endpoint. Before processing any incoming event, you should validate this signature.

These code samples demonstrate how to validate the signature of a webhook payload to ensure its authenticity. Make sure to replace your-secret-hash with your actual secret key for proper validation.

Here’s how to validate the signature in Node.js, Java, and C#:

Node.js Implementation

const crypto = require('crypto');
const express = require('express');
const app = express();

app.use(express.json()); // Parse JSON payload

const secretHash = 'your-secret-hash';

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-signature'];
  const payload = JSON.stringify(req.body);

  const computedSignature = crypto
    .createHmac('sha256', secretHash)
    .update(payload)
    .digest('base64');

  if (signature === computedSignature) {
    console.log('Valid webhook event:', req.body);
    res.sendStatus(200); // Respond with success
  } else {
    console.error('Invalid signature');
    res.sendStatus(400); // Respond with error
  }
});

app.listen(3000, () => {
  console.log('Listening on port 3000');
});

Java Implementation

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import javax.servlet.http.*;
import java.io.BufferedReader;

public class WebhookServlet extends HttpServlet {
    private static final String SECRET_HASH = "your-secret-hash";

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        try {
            String signature = request.getHeader("X-Signature");
            StringBuilder payload = new StringBuilder();
            BufferedReader reader = request.getReader();
            String line;
            while ((line = reader.readLine()) != null) {
                payload.append(line);
            }

            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(SECRET_HASH.getBytes(), "HmacSHA256"));
            byte[] computedHash = mac.doFinal(payload.toString().getBytes());

            String computedSignature = Base64.getEncoder().encodeToString(computedHash);

            if (signature.equals(computedSignature)) {
                System.out.println("Valid webhook event: " + payload);
                response.setStatus(HttpServletResponse.SC_OK);
            } else {
                System.out.println("Invalid signature");
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            }
        } catch (Exception e) {
            e.printStackTrace();
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }
}

C# Implementation

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc;

[Route("webhook")]
[ApiController]
public class WebhookController : ControllerBase
{
    private const string SecretHash = "your-secret-hash";

    [HttpPost]
    public IActionResult HandleWebhook([FromBody] object payload)
    {
        try
        {
            string signature = Request.Headers["X-Signature"];
            string payloadString = System.Text.Json.JsonSerializer.Serialize(payload);

            using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(SecretHash)))
            {
                var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payloadString));
                var computedSignature = Convert.ToBase64String(computedHash);

                if (computedSignature == signature)
                {
                    Console.WriteLine("Valid webhook event: " + payloadString);
                    return Ok();
                }
                else
                {
                    Console.WriteLine("Invalid signature");
                    return BadRequest();
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
            return StatusCode(500, "Internal server error");
        }
    }
}