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 debitTransactions session creditTransactions session rollbackUsers (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:
requestbin.com is a great tool for testing webhooks
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}!`));<?php
// Webhook secret key (provided by Dyce)
define('WEBHOOK_SECRET', 'your_secret_key_here');
// Function to verify the signature
function verifySignature($rawBody, $signature) {
$calculatedSignature = hash_hmac('sha256', $rawBody, WEBHOOK_SECRET);
return hash_equals($calculatedSignature, $signature);
}
// Receive HTTP POST requests
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_DYCE_SIGNATURE'] ?? '';
// Verify the signature
if (!verifySignature($rawBody, $signature)) {
http_response_code(400);
echo 'Invalid signature';
exit;
}
// Parse the JSON payload
$payload = json_decode($rawBody, true);
if (!$payload) {
http_response_code(400);
echo 'Invalid JSON payload';
exit;
}
$action = $payload['action'] ?? '';
$data = $payload['data'] ?? [];
$type = $payload['type'] ?? '';
$createdAt = $payload['createdAt'] ?? '';
// Do something with the data received
// ...
// Respond with HTTP 200 to signal successful receipt
http_response_code(200);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: 766e1d90a96e2f5ecec342a99c5552999dd95d49250171b902d703fd674f5086Where 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"
}<?php
// Provided webhook secret key
define('WEBHOOK_SECRET', 'your_secret_key_here');
// Get raw request body
$raw_body = file_get_contents('php://input');
// Get the Dyce-Signature header
$dyce_signature = $_SERVER['HTTP_X_DYCE_SIGNATURE'];
// Calculate HMAC of raw body using secret key
$calculated_signature = hash_hmac('sha256', $raw_body, WEBHOOK_SECRET);
// Compare calculated signature with Dyce-Signature header
if (!hash_equals($calculated_signature, $dyce_signature)) {
// Signature mismatch, invalid request
http_response_code(400);
echo 'Invalid signature';
exit;
}
// Signature is valid, process the payload
$payload = json_decode($raw_body, true);
// Access payload data
$action = $payload['action'];
$data = $payload['data'];
$type = $payload['type'];
$created_at = $payload['createdAt'];
// ... process the payload data as needed
// Respond with HTTP 200 OK
http_response_code(200);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
Dyce will encrypt the JSON payload data using a symmetric encryption algorithm like AES-256 with a secret key.
The encrypted payload will be sent in the webhook request body as a base64-encoded string.
An additional header
Dyce-Encryptedwith the valuetruewill 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
// ...<?php
// Provided encryption key (must be 32 characters for AES-256)
define('ENCRYPTION_KEY', 'your_32_char_encryption_key_here');
// Get the encrypted payload from the request body
$encryptedPayload = base64_decode(file_get_contents('php://input'));
// Create an initialization vector (IV)
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
// Decrypt the payload
$decryptedPayload = openssl_decrypt($encryptedPayload, 'aes-256-cbc', ENCRYPTION_KEY, OPENSSL_RAW_DATA, $iv);
// Convert the decrypted payload to an array
$payload = json_decode($decryptedPayload, true);
// 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:
Get the encrypted payload data from the request body (base64-decoded).
Generate a random initialization vector (IV) for the decryption process.
Create a decryption object/function using the AES-256-CBC algorithm with the provided encryption key and IV.
Decrypt the encrypted payload data.
Convert the decrypted data (which should be a JSON string) to a JSON object/array.
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.
Make sure to securely store and manage the encryption key, as it is a critical secret for ensuring data confidentiality.
For additional information on AES, there are a number of good resources:
Last updated