Webhooks are a way for a merchant to receive Signifyd decisions asynchronously.

Signifyd's system supports webhook integrations that allow merchants to receive real-time antifraud evaluations asynchronously. By registering a webhook, your application will automatically receive notifications when an evaluation is complete, enabling you to streamline fraud management without constant API polling. Read on for further details about the content of webhook notifications and managing registered webhooks with Signifyd.

Webhook Events

You can create webhooks in Signifyd for the following events. Each event has a corresponding topic identifier which will be sent in the signifyd-checkpoint header of the webhook.

Currently, the following events can trigger a webhook. Only one URL may be specified per event.

Eventsignifyd-checkpoint Header ValueDescriptionResponse
Merchant ReviewMERCHANT_REVIEWSent any time a user assigns a case a Review Disposition (thumbs up/down on console)View
Signifyd ReviewSIGNIFYD_REVIEWSent any time a decision is made by Signifyd agent on a caseView
CheckoutCHECKOUTSent any time a decision is made in response to a checkout eventView
SaleSALESent any time a decision is made in response to a sale eventView
TransactionTRANSACTIONSent any time a decision is made in response to a transaction eventView
RerouteREROUTESent any time a decision is made in response to a reroute eventView

Webhook Verification

To allow a client to verify a webhook message has in fact come from Signifyd, an SIGNIFYD-SEC-HMAC-SHA256 header is included in each webhook POST message. The contents of this header is the Base64 encoded output of the HMAC SHA256 encoding of the JSON body of the message, using the team's API key as the encryption key. Here's an example of how to compute the Json body as an HMAC encoded value in Java.

String teamApiKey = "testKey";
String hmacHeaderKey = "SIGNIFYD-SEC-HMAC-SHA256";
String webhookBody = request.body().asText();

// Hash and encode the webhookBody
Mac sha256HMAC = javax.crypto.Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(teamApiKey.getBytes(), "HmacSHA256");
sha256HMAC.init(secretKey);
String checkHash = Base64.encodeBase64String(sha256HMAC.doFinal(webhookBody.getBytes(StandardCharsets.UTF_8)));

// Compare the original hmacHeader with the new checkHash
boolean isValidRequest = request.getHeaders()
                                .get(hmacHeaderKey)
                                .map(hmacHeader -> hmacHeader.equals(checkHash))
                                .orElse(false);
const teamApiKey = "testKey";
const hmacHeaderKey = "SIGNIFYD-SEC-HMAC-SHA256";
// Note: Avoid mapping the request body to an object prior to hmac validation
const webhookBody = req.body.toString('utf8');

// Hash and encode the webhookBody
const crypto = require('crypto');
const checkHash = crypto.createHmac('sha256', teamApiKey)
												.update(webhookBody, 'utf8')
												.digest('base64');

// Compare the original hmacHeader with the new checkHash
const hmacHeader = req.header(hmacHeaderKey);
const isValidRequest = (typeof hmacHeader !== "undefined") 
                            ? hmacHeader == checkHash 
                            : false;
$teamApiKey = 'testKey';
/* Note: Signifyd's hmac header is 'SIGNIFYD-SEC-HMAC-SHA256'
   but PHP changes this to 'HTTP_SIGNIFYD_SEC_HMAC_SHA256' on $_SERVER */
$hmacHeaderKey = 'HTTP_' . str_replace('-', '_', 'SIGNIFYD-SEC-HMAC-SHA256');
$webhookBody = file_get_contents("php://input");

// Hash and encode the webhookBody
$checkHash = base64_encode(hash_hmac('sha256', $webhookBody, $teamApiKey, true));

// Compare the original hmacHeader with the new checkHash
$isValidRequest = isset($_SERVER[$hmacHeaderKey]) 
                        ? ($_SERVER[$hmacHeaderKey] == $checkHash ? true : false) 
                        : false;

Webhook Response

For field definitions, please refer to the documentation for the Get Decision endpoint's 201 response

{
  "signifydId": 44,
  "orderId": "XGR-1840823423",
  "decision": {
    "createdAt": "2020-11-20T20:16:15.382889Z",
    "checkpointAction": "ACCEPT",
    "checkpointActionReason": "Power buyer on approve list",
    "checkpointActionPolicy": "APPROVE_POWER_BUYERS",
    "policies": {
      "default": {
        "name": "SIGNIFYD_DECISION",
        "status": "EVALUATED_TRUE",
        "action": "REJECT",
        "reason": "Suspicious user profile"
      },
      "overriding": [
        {
          "name": "APPROVE_COOL_BUYERS",
          "status": "EVALUATED_TRUE",
          "action": "ACCEPT",
          "reason": "Buyer was cool"
        }
      ]
    },
    "score": 0
  },
  "coverage": {
    "fraudChargebacks": {
      "amount": 105.99,
      "currency": "GBP"
    },
    "inrChargebacks": {
      "amount": 105.99,
      "currency": "GBP"
    },
    "allChargebacks": {
      "amount": 105.99,
      "currency": "GBP"
    }
  }
}