Skip to main content
We send you the following webhooks:
  • checkout.succeeded - a payment was successful
  • checkout.failed - a payment failed
  • checkout.hold - during withdrawal, we ask you to place a hold on the available balance to prevent double spending and verify balance is sufficient.
  • checkout.release_hold - a withdrawal failed after you placed a hold.
  • checkout.returned - a payment was returned
  • checkout.pending - a checkout session has entered a pending state
  • checkout.terminally_failed - a checkout failed due to geo, kyc, or other fraud checks.
  • checkout.expired - a checkout expired (for security purposes all checkouts expire)

Verifying Webhook Signatures

In order to prevent mailicious actors, we include a signature in the webhook event as a headerSOAP-WEBHOOK-SIGNATURE. This signature was generated using your webhook signing secret that you can access in your dashboard in the Developers sections. The SOAP-WEBHOOK-SIGNATURE header has the following format:
t=1673979302,v1=6ffbb59b2300aae63f272406069a9788598b792a944a07aba816edb039989a39
t is the timestamp when the signature was generated and should be used to prevent replay attacks. v1 is the signature itself, generated using HMAC-SHA256. In order to verify the signature, you will use the event payload and recreate the signature using your webhook signing secret, and compare it to the signature you received.
import crypto from 'crypto';

function verifySignature(payload: string, signatureHeader: string, signingSecret: string): boolean {
  const parts = signatureHeader.split(",");
  const timestampPart = parts.find((p) => p.startsWith("t="));
  const signaturePart = parts.find((p) => p.startsWith("v1="));

  if (!timestampPart || !signaturePart) {
      return false;
  }

  const timestamp = timestampPart.split("=")[1];
  const receivedSignature = signaturePart.split("=")[1];

  // Create the signature message
  const message = `${timestamp}.${payload}`;
  
  // Calculate signature
  const calculatedSignature = crypto
      .createHmac("sha256", signingSecret)
      .update(message)
      .digest("hex");

  // Compare signatures using a timing-safe comparison
  return crypto.timingSafeEqual(
      Buffer.from(receivedSignature, "hex"),
      Buffer.from(calculatedSignature, "hex")
  );
}

If the signatures dont match you should reject the webhook and not process it.

Updating Player’s Balance

It really depends on how you implement your accouting system. In this example we are only using credits and debits and updating the player’s balance accordingly.

Other

  • We retry webhooks 4 times after the first attempt with the exception of checkout.hold.
  • There is a 10 second timeout.
  • Each webhook comes with event_id — use this as your idempotency key to not reprocess events.