# Guide to Solidgate Webhooks: Features and Best Practices

Solidgate webhooks notify your systems about payment activity: an order updates, a chargeback is received, a subscription changes. If you're building payments on Solidgate, webhooks are how you react to these events without polling.

This guide covers how Solidgate webhooks work, the events you'll handle, its unusual signature scheme (get this exactly right), the retry behavior, and the best practices for production.

## What are Solidgate webhooks?

Solidgate webhooks are HTTP POSTs delivered to endpoints you configure. Each carries two headers: `merchant` (your webhook public key, prefixed `wh_pk_`) and `signature` (the computed signature). The signature scheme is unusual and easy to get wrong, so the securing section below is the part to focus on.

## Solidgate webhook features

| Feature | Details |
| --- | --- |
| Configuration | Solidgate Hub: keys under Developers; endpoints/events under Developers > Channels |
| Headers | `merchant` (public key, `wh_pk_`), `signature` |
| Signature scheme | `base64(hex(HMAC-SHA512(secretKey, publicKey + rawBody + publicKey)))` |
| Keys | `wh_pk_` (public) / `wh_sk_` (secret) |
| Retries | 8 attempts: 15m, 30m, 1h, 2h, 4h, 8h, 16h, 24h |
| Acknowledgement | 2xx within 30 seconds |
| SDK | `@solidgate/node-sdk`, `solidgate-sdk` (no dedicated webhook-verify helper) |

## Common events

Solidgate event names are dotted and product-scoped:

| Event | Fires when |
| --- | --- |
| `card_gate.order.updated` | A card order updates |
| `card_gate.chargeback.received` | A chargeback is received |
| `alt_gate.order.updated` | An alternative-payment order updates |
| `subscription.updated.v2` | A subscription updates (the `.v2` suffix is real) |

Subscribe to the events you process, and consult Solidgate's webhook reference for the full list.

## Setting up Solidgate webhooks

Set your webhook keys in Solidgate Hub > Developers, and configure endpoints and events under Developers > Channels. Each delivery carries the `merchant` (public key) and `signature` headers you verify with.

## Securing Solidgate webhooks

The scheme has a distinctive double-encode. Construct `text = publicKey + rawBody + publicKey`, compute `HMAC-SHA512(secretKey, text)`, take the hex string of that digest, then base64-encode the hex string (not the raw digest bytes). Compare the result to the `signature` header. Use the secret key (`wh_sk_`) and the exact raw body.

```javascript
const crypto = require("crypto");

const SECRET_KEY = process.env.SOLIDGATE_WEBHOOK_SECRET_KEY; // wh_sk_

function verify(rawBody, publicKey, signature) {
  const text = publicKey + rawBody.toString() + publicKey;
  const hex = crypto.createHmac("sha512", SECRET_KEY).update(text).digest("hex");
  // base64 of the HEX STRING, not the raw digest bytes
  const expected = Buffer.from(hex).toString("base64");

  const a = Buffer.from(expected);
  const b = Buffer.from(signature || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const publicKey = req.headers["merchant"];
  if (!verify(req.body, publicKey, req.headers["signature"])) {
    return res.sendStatus(401);
  }

  res.sendStatus(200); // acknowledge within 30s
  processQueue.add(JSON.parse(req.body)); // process asynchronously
});

```

The same in Python (matching Solidgate's official example):

```python
import base64
import hashlib
import hmac
import os

SECRET_KEY = os.environ["SOLIDGATE_WEBHOOK_SECRET_KEY"].encode()  # wh_sk_

def verify(raw_body: bytes, public_key: str, signature: str) -> bool:
    text = (public_key.encode() + raw_body + public_key.encode())
    hmac_hash = hmac.new(SECRET_KEY, text, hashlib.sha512).digest()
    # base64 of the hex string (Solidgate's official construction)
    expected = base64.b64encode(hmac_hash.hex().encode("utf-8")).decode("utf-8")
    return hmac.compare_digest(expected, signature or "")

```

## Solidgate webhook limitations and pain points

### The double-encode is easy to get wrong

The Problem: The signature is `base64(hex(HMAC-SHA512(...)))`, base64 of the hex string, not base64 of the raw digest bytes. And the signed text wraps the body in the public key (`publicKey + body + publicKey`). Almost every "obvious" implementation (base64 of the digest, or signing the body alone) fails.

Why It Happens: Solidgate's official construction double-encodes and brackets the body with the public key.

Workarounds:

* Follow the construction exactly: `text = pk + body + pk`, HMAC-SHA512 with `wh_sk_`, hex, then base64 the hex string.

How Hookdeck Can Help: Hookdeck verifies the Solidgate signature at the edge, so your application receives pre-verified events without reproducing the double-encode.

### Raw body sensitivity

The Problem: The body is wrapped into the signed text verbatim, so any re-serialization breaks verification.

Why It Happens: The signature is over the exact raw body between the two public-key copies.

Workarounds:

* Capture the raw body and verify before parsing.

How Hookdeck Can Help: Hookdeck verifies against the bytes as received, so your app never has to preserve the raw body.

### No SDK verify helper

The Problem: The official SDKs exist but don't ship a dedicated webhook-verification helper, so you implement the scheme yourself.

Why It Happens: The SDKs target the payment APIs, not webhook ingestion.

Workarounds:

* Use the constant-time implementation above, centralized in one place.

How Hookdeck Can Help: Hookdeck verifies the signature at the edge, so your app receives pre-verified events without hand-rolled crypto.

### Retries and duplicates

The Problem: Solidgate retries 8 times (15m through 24h), so the same event can arrive more than once, and double-processing a payment event is costly.

Why It Happens: At-least-once delivery favors eventual delivery.

Workarounds:

* Dedupe on an event/order id, make side effects idempotent, and confirm state via the API before releasing value.

How Hookdeck Can Help: Hookdeck deduplicates deliveries at the edge, so retries don't double-process. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

## Best practices

### Verify with the exact double-encode

`text = pk + rawBody + pk`, HMAC-SHA512 with `wh_sk_`, hex, then base64 the hex string; compare in constant time to `signature`.

### Verify against the raw body

Capture the raw body so the wrapped signed text matches.

### Acknowledge within 30 seconds, process asynchronously

Return 2xx quickly and defer work to a queue. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

### Dedupe and confirm before settling

Dedupe on an event/order id, and confirm payment state via the API before releasing value.

## Conclusion

Solidgate webhooks use an unusual signature: `base64(hex(HMAC-SHA512(secretKey, publicKey + rawBody + publicKey)))`, delivered with `merchant` and `signature` headers. The double-encode and the public-key-wrapped body are the things to get exactly right; then verify against the raw body, dedupe across the 8-attempt retry schedule, and confirm state before releasing value.

[Hookdeck](https://hookdeck.com) verifies the signature, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique payment events.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Solidgate webhooks reliably in minutes.