Skip to main content
When PayInGame sends a webhook to your endpoint, it includes a header called:
Payingame-Signature
This header lets you verify that the webhook was sent by PayInGame, not a malicious third party.

Header Structure

The header looks like this:
t=1762795211,v1=d4ec6316d9dcbbc06db0a18bdf8cdb73c8865d4d5134c9890ba39a15a8f17b26
  • t: Unix timestamp (in seconds) when the webhook was generated
  • v1: HMAC-SHA256 signature of the payload, computed using your secret key

How It Works

When your project’s webhook is triggered, we compute:
signedPayload = “timestamp.raw_body”
Then we sign that payload using your webhook secret key (a 256-bit hex string) with HMAC-SHA256:
hmacValue = hmac(signedPayload, secretKey, “HMACSHA256”, “UTF-8”)

Example

Example webhook payload:
{
  "PaymentGuid": "9C4E0E58-ABF8-DFC3-D130-EF993228349F",
  "ProjectGuid": "5E3E59A2-FC03-88DE-6135-C05FAE5BA7B2",
  "Quantity": 1,
  "Products": [
    "7BC62A19-E33F-E99D-F582-B720FF46A8CA",
    "7BC62A19-E33F-E99D-F582-B720FF46A8CA"
  ],
  "UserID": "Cus123"
}
translates into string: {"PaymentGuid":"9C4E0E58-ABF8-DFC3-D130-EF993228349F","ProjectGuid":"5E3E59A2-FC03-88DE-6135-C05FAE5BA7B2","Quantity":1,"Products":["7BC62A19-E33F-E99D-F582-B720FF46A8CA","7BC62A19-E33F-E99D-F582-B720FF46A8CA"],"UserID":"Cus123"} This is the raw_body that is used later on. Example signature header:
Payingame-Signature: t=1762795211,v1=36DCF83BDD5DD52F29A37091A78A0906285BCB7FBFA40DD829D26FEF81956F0B

Example secret key:
e3cf0f521274f2badab694b0b8c861823aae5b33a59eb4809332dc03bdb9297b
Computed HMAC:
hmac("1762795211.raw_body", secretKey, "HMACSHA256")
This results in:
36DCF83BDD5DD52F29A37091A78A0906285BCB7FBFA40DD829D26FEF81956F0B
Receiver side (example .net)
using System;
using System.Linq;
using System.Text;
using System.Security.Cryptography;

public class Program
{
    public static void Main()
    {
        const string sigHeader = "t=1762795211,v1=36DCF83BDD5DD52F29A37091A78A0906285BCB7FBFA40DD829D26FEF81956F0B";

        // This is the **exact minified JSON** that the signature was generated from.
        // Any formatting (extra spaces, newlines, tabs) will change the computed HMAC.
        string body = """
{"PaymentGuid":"9C4E0E58-ABF8-DFC3-D130-EF993228349F","ProjectGuid":"5E3E59A2-FC03-88DE-6135-C05FAE5BA7B2","Quantity":1,"Products":["7BC62A19-E33F-E99D-F582-B720FF46A8CA","7BC62A19-E33F-E99D-F582-B720FF46A8CA"],"UserID":"Cus123"}
""";

        ReceiveWebhook(body, sigHeader);
    }

    // Your 256-bit hex secret from the PayInGame dashboard
    private const string SecretKey = "e3cf0f521274f2badab694b0b8c861823aae5b33a59eb4809332dc03bdb9297b";

    public static void ReceiveWebhook(string body, string sigHeader)
    {
        // Normalize potential CRLF issues (to ensure consistent body hashing)
        body = body.Replace("\r\n", "\n");

        // 1️⃣ Parse the t= and v1= values
        var parts = sigHeader
            .Split(',', StringSplitOptions.RemoveEmptyEntries)
            .Select(p => p.Trim().Split('='))
            .Where(p => p.Length == 2)
            .ToDictionary(p => p[0], p => p[1]);

        if (!parts.TryGetValue("t", out var timestamp))
        {
            Console.WriteLine("Missing timestamp");
            return;
        }

        if (!parts.TryGetValue("v1", out var receivedSignature))
        {
            Console.WriteLine("Missing signature");
            return;
        }

        // 2️⃣ Build message to verify: "timestamp.body"
        var message = $"{timestamp}.{body}";
		Console.WriteLine(message);

        // 3️⃣ Compute HMAC-SHA256 using the shared secret
        var secretBytes = Encoding.UTF8.GetBytes(SecretKey);

        string computedSignature;
        using (var hmac = new HMACSHA256(secretBytes))
        {
            var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
            computedSignature = Convert.ToHexString(hash);
        }

        // 4️⃣ Compare safely (constant time)
        if (SecureEquals(computedSignature, receivedSignature))
        {
            Console.WriteLine("✅ Verified webhook!");
        }
        else
        {
            Console.WriteLine($"❌ Invalid signature:\nExpected: {receivedSignature}\nComputed: {computedSignature}");
        }
    }

    private static bool SecureEquals(string a, string b)
    {
        if (a.Length != b.Length) return false;
        int diff = 0;
        for (int i = 0; i < a.Length; i++)
            diff |= a[i] ^ b[i];
        return diff == 0;
    }
}