Skip to content
Gurdx

Docs / Platform

Webhooks

Webhooks let Gurdx push a signed JSON message to your server the moment a risky event is detected, so you can react without polling.

How it works#

When an API request produces a risky result (for example a suspicious IP or a high-risk payment), Gurdx stores an event and then queues one delivery per enabled webhook that subscribes to that event type. Each delivery is an HTTPS POST with a JSON body and a signature header. Test-mode traffic (mode=test or a gx_test_ key) never raises events, so it never triggers webhooks.

Create webhooks in the dashboard at https://gurdx.cretip.com/app under Settings. Each webhook has a URL, a list of subscribed event types (or * for all) and a secret used for signing.

Event names#

Webhooks use stable public event names. One name differs from the internal event type: the internal type suspicious_ip is delivered as proxy_detected.

Delivered event Internal type (dashboard) Raised by
proxy_detected suspicious_ip IP geolocation, IP lookup, bulk lookup, IP reputation
fraud_payment fraud_payment Payment fraud scoring
spam_email spam_email Email scoring
spam_phone spam_phone Phone validation
profanity profanity Profanity detection
invalid_iban invalid_iban IBAN lookup
invalid_bin invalid_bin BIN lookup

Note: When you choose which events a webhook subscribes to, you pick from the internal types shown in the dashboard (so VPN and proxy detections are suspicious_ip there). Your receiver always sees the delivered name from the first column, in both the body and the X-Gurdx-Event header.

Request headers#

Header Value
Content-Type application/json
User-Agent Gurdx-Webhooks/1.0
X-Gurdx-Event The delivered event name, e.g. proxy_detected
X-Gurdx-Delivery The numeric event ID. It stays the same across retries
X-Gurdx-Signature sha256= followed by the hex HMAC-SHA256 of the raw body

Payload#

The body starts with the event name, followed by the event details, then the risk score, the user identifier you supplied through userID (or null) and an ISO-8601 timestamp.

{
  "event": "proxy_detected",
  "ip": "203.0.113.42",
  "countryCode": "NL",
  "security": { "isProxy": true, "proxyType": "VPN", "isTor": false },
  "risk_score": 78.5,
  "user_identifier": "user_1042",
  "occurred_at": "2026-10-06T09:14:22+00:00"
}

The detail fields between event and risk_score depend on the endpoint that raised the event. Always code against event, risk_score, user_identifier and occurred_at, and treat the rest as optional.

Verify the signature#

The signature is computed over the exact bytes of the request body, using the webhook secret shown when you created the webhook. Always verify before parsing the JSON, and compare in constant time.

<?php
$secret = getenv('GURDX_WEBHOOK_SECRET');
$raw = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_GURDX_SIGNATURE'] ?? '';

$expected = 'sha256=' . hash_hmac('sha256', $raw, $secret);

if (! hash_equals($expected, $header)) {
    http_response_code(401);
    exit('invalid signature');
}

$event = json_decode($raw, true);
// ...handle $event['event']
http_response_code(204);
import crypto from 'node:crypto';
import express from 'express';

const app = express();

// Keep the raw body: re-serialising parsed JSON changes the bytes.
app.post('/gurdx', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.GURDX_WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');
  const received = req.get('X-Gurdx-Signature') || '';

  const a = Buffer.from(expected);
  const b = Buffer.from(received);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString('utf8'));
  // ...handle event.event
  res.sendStatus(204);
});

Delivery and retries#

  • Gurdx waits up to 10 seconds for a response. Any 2xx status counts as success.
  • Anything else (non-2xx, timeout, connection error) is retried. A delivery is attempted at most 5 times, with waits of 10 seconds, 1 minute, 5 minutes and 30 minutes between attempts.
  • Every attempt is logged with its status code, response excerpt and duration.
  • After 20 consecutive failed attempts on a webhook, Gurdx disables it automatically. A successful delivery resets the counter. Re-enable it from the dashboard after fixing your endpoint.

Idempotency#

Retries and rare duplicate queue runs mean you can receive the same event more than once. Use the X-Gurdx-Delivery header (the event ID) as an idempotency key: store it and ignore a delivery you already processed.

Best practices#

  1. Verify the signature before parsing or trusting anything in the body.
  2. Respond with 2xx quickly, then do the heavy work in a background job.
  3. Treat risk_score as a signal, not a verdict, and combine it with your own context.
  4. Keep the secret in an environment variable and rotate it if it leaks.
  5. Serve the endpoint over HTTPS and log the X-Gurdx-Delivery ID with each handled event.

See also Events and alerts and Integrations overview.

Found a mistake? Tell us on the contact page. Contact