Skip to main content

Cardholder Alerts

The alerts generated by the antifraud tool are notified through Webhook. To enable that, you need to work with our support team to configure an endpoint address where we will send the notifications, as well as a secret_token that will be used to sign the request.

In this notification we send information about the alerts generated, along with which cardholder they refer to, so that the client can take some action — for example, sending a push notification to the cardholder.

Request

Request Body
{
"alert_key": "123456",
"cardholder_id": "ef47bc3f-61ac-4b85-ad67-0cfa3a422201",
"company_name": "Cliente 1",
"irregularity_type" : "fraud",
"risk_level": "critical"
}

The request has the format above and notifies the opening of a new alert for a cardholder — described by the cardholder_id.

Webhook signature

To guarantee that the request received at your endpoint came from our servers, we send an HMAC signature in the Signature header. You recompute that signature on your side and compare it with the one received — if they match, the request is trustworthy.

How the signature is computed

Signature = HMAC-SHA1(signature_key, endpoint + method + payload) → hexadecimal

The three components are concatenated in this order, with no separator:

ComponentWhat it is
endpointThe full URL of your webhook, exactly as configured with support (including https:// and any query string).
methodThe HTTP verb in uppercase — always POST for alert notifications.
payloadThe request body exactly as received, byte for byte.
signature_keyThe secret_token you agreed on with support. It is the HMAC key, not part of the message.
Use the raw body, never re-serialized JSON

The signature is computed over the exact bytes of the body. If you deserialize the JSON and serialize it again before validating, key order and whitespace change, and the signature will never match.

Read the body as a raw string/bytes first, validate the signature, and only then parse it. In the examples below this appears as request.data, file_get_contents('php://input'), req.rawBody and so on.

Accented characters in the payload

We serialize the body with ensure_ascii=False, meaning accented characters are sent as literal UTF-8 ("João") rather than escaped ("João"). Treat the body as UTF-8 when computing the HMAC — this is the default in every language below, but it is the most common cause of a mismatched signature when company_name contains an accent.

Validation examples

import hashlib
import hmac

SIGNATURE_KEY = "YOUR_SECRET_TOKEN"
WEBHOOK_URL = "https://seu-dominio.com/webhooks/qitech/alertas"


def calculate_signature(endpoint: str, method: str, payload: str) -> str:
hmac_obj = hmac.new(
SIGNATURE_KEY.encode("utf-8"),
(endpoint + method + payload).encode("utf-8"),
hashlib.sha1,
)
return hmac_obj.hexdigest()


def is_valid(received_signature: str, raw_body: str) -> bool:
expected = calculate_signature(WEBHOOK_URL, "POST", raw_body)
# compare_digest prevents timing attacks
return hmac.compare_digest(expected, received_signature)


# Example with Flask
from flask import Flask, request

app = Flask(__name__)


@app.route("/webhooks/qitech/alertas", methods=["POST"])
def receive_alert():
raw_body = request.get_data(as_text=True) # raw body, no parsing
received = request.headers.get("Signature", "")

if not is_valid(received, raw_body):
return "", 401

alert = request.get_json() # parse only after validating
print(alert["cardholder_id"], alert["risk_level"])
return "", 200
Signature not matching? Check in this order
  1. Was the body re-serialized? This is the most frequent cause. Use the raw body.
  2. Is the URL identical? A trailing slash added or removed (/alertas vs /alertas/) changes the signature. Use exactly the URL configured with support.
  3. Is the method uppercase? It must be POST, not post.
  4. Is the concatenation order right? It is endpoint + method + payload, in that order.
  5. Is the digest lowercase hexadecimal? It is not Base64.

Retries

A notification is considered delivered when it receives an HTTP Status 200 in response. If the notifications fail, 5 retries will be made, at the following intervals, until a 200 is returned or the attempts run out:

  • 30 seconds
  • 60 seconds
  • 120 seconds
  • 240 seconds
  • 360 seconds