Gareth Wilson Gareth Wilson

Guide to Tebex Webhooks: Features and Best Practices

Published


Tebex webhooks notify your systems about monetization activity in your store: a payment completes, a payment is refunded or disputed, a recurring payment renews. If you're building on Tebex, webhooks are how you react to purchases without polling.

This guide covers how Tebex webhooks work, the events you'll handle, its two-step signature (get this exactly right), the validation ping, IP allowlisting, and the best practices for production.

What are Tebex webhooks?

Tebex webhooks are HTTP POSTs delivered to endpoints you configure per store. Each carries an X-Signature header, and the signature construction is unusual: it's a two-step hash, not a plain HMAC of the body. Tebex also allowlists its source IPs and sends a validation ping you must answer to activate an endpoint.

Tebex webhook features

FeatureDetails
ConfigurationCreator Panel > Developers > Webhooks > Endpoints (per-endpoint secret + event types)
Signature headerX-Signature (hex)
Signature schemeHMAC-SHA256(sha256(rawBody), secret), hex (two-step)
Source IPs18.209.80.3, 54.87.231.232 (docs suggest returning 404 for others)
Setup pingvalidation.webhook: respond 200 with {"id": "<webhook id>"}
AcknowledgementAlways respond 2XX; any other status triggers retries
SDKNone

Common events

Tebex event types are dotted:

EventFires when
payment.completedA payment completes
payment.declinedA payment is declined
payment.refundedA payment is refunded
payment.dispute.openedA dispute is opened (.won / .lost / .closed follow)
recurring-payment.renewedA recurring payment renews
validation.webhookThe setup ping (respond with the webhook id)

Recurring events also include recurring-payment.started / .ended and recurring-payment.cancellation.requested / .aborted. Select event types per endpoint in the Creator Panel.

Setting up Tebex webhooks

Configure endpoints under Creator Panel > Developers > Webhooks > Endpoints, set the secret, and select event types. On setup, Tebex sends a validation.webhook event; respond 200 with {"id": "<the webhook id>"} echoing the id, or the endpoint won't activate. Optionally restrict the endpoint to Tebex's source IPs (returning 404 for others).

Securing Tebex webhooks

The signature is two-step: first SHA256-hash the raw body, then HMAC-SHA256 that hash with your webhook secret, hex-encoded. It is not a plain HMAC of the body. Use the raw body (JSON body-parsers break the signature).

const crypto = require("crypto");

const SECRET = process.env.TEBEX_WEBHOOK_SECRET;
const ALLOWED_IPS = new Set(["18.209.80.3", "54.87.231.232"]);

function verify(rawBody, signature) {
  // Two-step: HMAC-SHA256( sha256(body), secret )
  const bodyHash = crypto.createHash("sha256").update(rawBody).digest("hex");
  const expected = crypto.createHmac("sha256", SECRET).update(bodyHash).digest("hex");
  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) => {
  if (!ALLOWED_IPS.has(req.ip)) return res.sendStatus(404);
  if (!verify(req.body, req.headers["x-signature"])) return res.sendStatus(401);

  const payload = JSON.parse(req.body);

  // Setup ping: echo the webhook id
  if (payload.type === "validation.webhook") {
    return res.status(200).json({ id: payload.id });
  }

  res.sendStatus(200); // always respond 2XX
  processQueue.add(payload); // process asynchronously
});

The same two-step check in Python (matching Tebex's PHP reference):

import hashlib
import hmac
import os

SECRET = os.environ["TEBEX_WEBHOOK_SECRET"].encode()


def verify(raw_body: bytes, signature: str) -> bool:
    body_hash = hashlib.sha256(raw_body).hexdigest()
    expected = hmac.new(SECRET, body_hash.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature or "")

Tebex webhook limitations and pain points

The two-step signature

The Problem: The signature is HMAC-SHA256(sha256(body), secret), you HMAC the SHA256 hash of the body, not the body. Almost every "obvious" implementation (a plain HMAC of the body) fails.

Why It Happens: Tebex's documented construction hashes the body first, then HMACs the hash.

Workarounds:

  • SHA256 the raw body, then HMAC-SHA256 that hash with the secret, hex-encode, and compare.

How Hookdeck Can Help: Hookdeck verifies the Tebex signature at the edge, so your application receives pre-verified events without reproducing the two-step hash.

The validation ping gates activation

The Problem: The endpoint won't activate until you answer the validation.webhook ping with {"id": "<webhook id>"}. Miss it and you receive nothing.

Why It Happens: Tebex validates the endpoint before delivering events.

Workarounds:

  • Detect validation.webhook and echo the id with a 200.

How Hookdeck Can Help: Hookdeck can answer the validation ping and forward verified events, so activation isn't a manual concern.

IP allowlisting and raw body

The Problem: Tebex delivers only from two IPs (and suggests returning 404 to others), and the signature is over the raw body, so a JSON parser upstream breaks verification.

Why It Happens: Tebex uses a fixed egress and signs the raw body.

Workarounds:

  • Allowlist the two IPs, and capture the raw body before parsing.

How Hookdeck Can Help: Hookdeck verifies against the bytes as received and gives you a dedicated ingestion URL, so IP and raw-body handling are managed for you.

Retries on non-2XX

The Problem: Any non-2XX response triggers retries, so a handler that errors out gets repeated deliveries.

Why It Happens: Tebex treats non-2XX as "not delivered".

Workarounds:

  • Return 2XX once verified and enqueued; handle processing errors internally, and dedupe.

How Hookdeck Can Help: Hookdeck returns success to Tebex and retries to your downstream on its own schedule, so a transient error doesn't cause a retry storm. See our guide to webhook idempotency.

Best practices

Verify the two-step signature over the raw body

SHA256 the raw body, HMAC-SHA256 that hash with your secret, hex-encode, and compare in constant time.

Answer the validation ping

Echo {"id": "<webhook id>"} with a 200 to activate the endpoint.

Allowlist the source IPs

Restrict deliveries to 18.209.80.3 and 54.87.231.232.

Always return 2XX, process asynchronously

Return 2XX once verified and defer work to a queue; handle failures internally. See why to process webhooks asynchronously.

Make Tebex webhooks production-ready

Hookdeck verifies the two-step signature, deduplicates, and durably queues every payment event

Conclusion

Tebex webhooks use a distinctive two-step signature, HMAC-SHA256(sha256(rawBody), secret), over the raw body, plus a validation.webhook ping you answer with the webhook id and a two-IP allowlist. Get the two-step hash right, activate via the ping, allowlist the IPs, and always return 2XX.

Hookdeck 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 for free and handle Tebex webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.