Webhooks

Dyce provides webhooks which allow you to receive HTTP push notifications whenever data is created or updated. This allows you to build integrations on top of Dyce. You could trigger CI builds, perform calculations on issue data, or send messages on specific conditions – you name it.

Webhooks are specific to a Brand, you can configure webhooks to provide updates from all type of transactions, or a single type to satisfy the needs to divide in your organization.

What we call "data change webhooks" are currently supported for the following models:

  • Transactions session debit

  • Transactions session credit

  • Transactions session rollback

  • Users (coming soon)

  • Wallets (coming soon)

  • Games (coming soon)

  • Game Providers (coming soon)

How does a Webhook work?

A Webhook push is simply a HTTP POST request, sent to the URL of your choosing. The push is automatically triggered by Dyce when data updates. For an example of what data a payload contains, see The Webhook Payload.

Your webhook consumer is a simple HTTP endpoint. It must satisfy the following conditions:

  • It's available in a publicly accessible HTTPS, non-localhost URL

  • It will respond to the Dyce Webhook push (HTTP POST request) with a HTTP 200 ("OK") response

If a delivery fails (i.e. server unavailable or responded with a non-200 HTTP status code), the push will be retried a couple of times. Here an exponential backoff delay is used: the attempt will be retried after approximately 10 minutes, then 30 minutes, and so on. If the webhook URL continues to be unresponsive the webhook might be disabled by Dyce, and must be re-enabled again manually.

To make sure a Webhook POST is truly created by Dyce, you can check the request to originates from one of the IPs provided.

For additional information on Webhooks, there are a number of good resources:

Getting started with Dyce Webhooks

You will first need to create a Webhook endpoint ("consumer") to be called by the Dyce Webhook agent. This can be a simple HTTP server you deploy yourself, or a URL endpoint configured by a service such as Zapier (or for testing purposes, RequestBin).

Once your consumer is ready to receive updates, you can ask us to enable it via Slack or Skype on the dedicated group.

Creating a simple Webhook consumer

You might consider using something like Netlify Functions, which provides a straightforward way of deploying simple HTTP(S) endpoints: https://www.netlify.com/blog/2018/09/13/how-to-run-express.js-apps-with-netlify-functions/.

Keeping the requirements in mind, a simple but workable Node.js/Express (v4) webhook consumer might look something like this:

const express = require("express");
const bodyParser = require("body-parser");
const app = express();
const port = 3000;

app.use(
  express.json({
    // Save raw body buffer before JSON parsing
    verify: (req) => {
      req.rawBody = buf;
    },
  })
);

// Parse the request body
app.use(bodyParser.json());

// Receive HTTP POST requests
app.post("/my-dyce-webhook", (req, res) => {
  const payload = req.body;
  const { action, data, type, createdAt } = payload;

  // Verify signature
  const signature = crypto.createHmac("sha256", WEBHOOK_SECRET).update(rawBody).digest("hex");
  if (signature !== request.headers['x-dyce-signature']) {
    res.sendStatus(400);
    return
  }

  // Do something neat with the data received!

  // Finally, respond with a HTTP 200 to signal all good
  res.sendStatus(200);
});

app.listen(port, () => console.log(`My webhook consumer listening on port ${port}!`));

Configuring with the Team

Specify us the URL in which you have an endpoint ready to receive HTTP POST requests.

Your newly created webhook will be listed and is ready to be used. Your defined URL of http://example.com/webhooks/dyce-updates will now get notified of any updates for your chosen models.

The Webhook Payload

The webhook HTTP payload will include information both in its HTTP headers and its request body.

The format of the payload body reflects that of the corresponding model entity. To get a hang of the data contained in the various objects, feel free to play around with model queries against Dyce's API.

The payload will be sent with the following HTTP headers:

Accept-Charset: utf-8
Content-Type: application/json; charset=utf-8
Dyce-Delivery: 234d1a4e-b617-4388-90fe-adc3633d6b72
Dyce-Event: Transaction Session Debit
User-Agent: Dyce-Webhook
X-Dyce-Signature: 766e1d90a96e2f5ecec342a99c5552999dd95d49250171b902d703fd674f5086

Where the custom headers include:

Name

Description

Dyce-Delivery

An UUID (v4) uniquely identifying this payload.

Dyce-Event

The Entity type which triggered this event: Transaction, User etc

X-Dyce-Signature

HMAC signature of the webhook payload

Data change events payload

These fields are present on all data change events.

Field

Description

action

The type of the action that took place: create, update or remove.

type

The type of entity that was targeted by the action.

createdAt

The date and time that the action took place.

data

The serialized value of the subject entity.

webhookTimestamp

UNIX timestamp when the webhook was sent.

webhookId

ID uniquely identifying this webhook.

For example:

{
  "action": "create",
  "data": {
    "id": 17724480,
    "provider_id": 32,
    "sub_provider_id": 12,
    "category_id": 1,
    "game_id": 3215,
    "user_id": 1699,
    "wallet_id": 2124,
    "reference_user_id": "",
    "description": "Reevo - Bet",
    "amount": 50,
    "currency": "XOF",
    "old_balance": 5002,
    "new_balance": 4952,
    "control_balance": 4952,
    "old_win": 5002,
    "new_win": 4952,
    "control_win": 4952,
    "old_deposit": 0,
    "new_deposit": 0,
    "control_deposit": 0,
    "old_bonus": 0,
    "new_bonus": 0,
    "control_bonus": 0,
    "has_bonus": 0,
    "bonus_amount": 0,
    "transaction_uuid": "hs-4005139713923",
    "bet_uuid": 4002573534965,
    "operator_transaction_uuid": 1.4217235956581843e+34,
    "operator_validation": 1,
    "is_test": 0,
    "created_at": "2024-02-17 23:59:56",
    "updated_at": "2024-02-17 23:59:58",
    "deleted_at": ""
  },
  "type": "TransactionSessionDebit",
  "createdAt": "2020-01-23T12:53:18.084Z",
  "webhookTimestamp": 1676056940508,
  "webhookId": "000042e3-d123-4980-b49f-8e140eef9329"
}

Securing webhooks

We support securing webhooks through content hashing with a signature. SHA256 HMAC signature is calculated of the content and delivered in X-Dyce-Signature header which can used for comparison. Content body also includes a field webhookTimestamp with UNIX timestamp of the time when webhook was sent. It's recommended you verify that it's within a minute of the time your system sees it to prevent replay attacks.

To verify the webhook, calculate the signature from request body using the webhook secret that will be given. It's recommended to use raw request body content for the hashing as using JSON parsing might change it.

const signature = crypto.createHmac("sha256", WEBHOOK_SECRET).update(rawBody).digest("hex");
if (signature !== request.headers['x-dyce-signature']) {
  throw "Invalid signature"
}

In addition to verifying webhook, it's recommended to validate the sender IP addresses.

Payload Encryption (Optional)

For an additional layer of security, Dyce offers the option to encrypt the webhook payload data before transmission. This ensures that the sensitive payload data remains confidential and can only be accessed by the intended recipient with the correct decryption key.

Encryption Process

  1. Dyce will encrypt the JSON payload data using a symmetric encryption algorithm like AES-256 with a secret key.

  2. The encrypted payload will be sent in the webhook request body as a base64-encoded string.

  3. An additional header Dyce-Encrypted with the value true will be included to indicate that the payload is encrypted.

Decryption Process

On the recipient's side (your server), you'll need to decrypt the payload before processing it. Here's how you can do it:

const crypto = require('crypto');

// Provided encryption key (must be 32 characters for AES-256)
const ENCRYPTION_KEY = 'your_32_char_encryption_key_here';

// Get the encrypted payload from the request body
const encryptedPayload = Buffer.from(req.body.data, 'base64');

// Create an initialization vector (IV)
const iv = crypto.randomBytes(16);

// Create a decipher object
const decipher = crypto.createDecipheriv('aes-256-cbc', ENCRYPTION_KEY, iv);

// Decrypt the payload
let decryptedPayload = decipher.update(encryptedPayload);
decryptedPayload = Buffer.concat([decryptedPayload, decipher.final()]);

// Convert the decrypted payload to a JSON object
const payload = JSON.parse(decryptedPayload.toString());

// Process the payload data
// ...

In both examples, you'll need to replace 'your_32_char_encryption_key_here' with the actual encryption key provided by Dyce.

The decryption process follows these steps:

  1. Get the encrypted payload data from the request body (base64-decoded).

  2. Generate a random initialization vector (IV) for the decryption process.

  3. Create a decryption object/function using the AES-256-CBC algorithm with the provided encryption key and IV.

  4. Decrypt the encrypted payload data.

  5. Convert the decrypted data (which should be a JSON string) to a JSON object/array.

  6. Process the decrypted payload data as needed.

By adding this encryption layer, the payload data will be securely transmitted, and only the intended recipient with the correct encryption key will be able to decrypt and access the data.

For additional information on AES, there are a number of good resources:

Last updated