🔔Webhook
How does it work?
Webhook is a kind of feedback method for payment information.
When invoice status changes, a POST request is sent to the callback url specified when creating the invoice.
Webhook Request format
{
"id": "9c3288f5-3aef-464d-a3fd-57c170163eab",
"amount": 9,
"crypto_amount": 0.0001855328,
"remaining_crypto_amount": 0,
"paid_fiat_amount": 9,
"paid_crypto_amount": 0.0001855328,
"fiat_currency": "USD",
"is_payment_multiple": true,
"status": "paid",
"currency": "BTC",
"network": "BTC",
"wallet": "bc1qkyuvunrr4h393vvjkn5w9pyljdnu6084v5tr6j",
"created_at": 1717408600,
"expires_at": 1717412200,
"pay_url": "https://pay.silus.io/9c3288f5-3aef-464d-a3fd-57c170163eab",
"additional_data": {
"user_id": 255,
"client_category": "Big"
},
"transactions": [
{
"transaction_id": "0226ac9c2f59684869c1733866b3c526644f1b7082412a359100c6470b8c06a3",
"source_wallet": "TVxxDaAB3Jc4HQnN6XtXqdzzGp835ihqyd",
"amount": 0.08027
}
]
}id*
Invoice id. Can be used to check it's status
amount*
Invoice amount in fiat currency
crypto_amount
Invoice amount in crypto. Can be null in case no strict currency/network provided
remaining_crypto_amount
Invoice remaining amount in crypto. Can be null in case no strict currency/network provided
paid_fiat_amount
Paid amount of the invoice in fiat currency
paid_crypto_amount
Paid amount of the invoice in crypto currency
fiat_currency*
Fiat currency from which order was created
is_payment_multiple
Whentether multiple payments are allowed for this invoice
status*
wallet
Wallet where we're expecting the payment to be. Can be omit in case no payment method selected
created_at*
Unix time for invoice creation date
expires_at*
Unix time for invoice expiration date
pay_url
Link which can be used to redirect user to our payment form
additional_data*
Any data provided during Invoice creation request. It will be null if you didn't specify it during invoice creation request.
transactions*
Array which contains list of transactions associated with this payment.
It can have multiple values in case is_payment_multiple = true and user paid separate payments
transactions.*.transaction_id
Transaction ID on blockhain
transactions.*.source_wallet
Wallet from which funds came from
transactions.*.amount
Amount of funds received on this transaction
* - mandatory parameter
Webhook verification
Since you release products or coins for users, it is important to ensure that all requests are sent from Silus. Every Webhook Request has two headers:
X-Silus-Sign— this header contains the signature which should be validated by your backend.X-Silus-Timestamp— this header contains the unix time when the event was sent. It will be used for signature generation.
How to make sure sign in request is valid?
To ensure that the signature in the request is valid, you must encode the entire webhook payload to JSON, then append the X-Silus-Timestamp value at the end of the resulting string. The entire result should be converted to SHA256 using HMAC with a secret key (the one you use to authenticate requests to the Silus API).
The resulting string should be equal to the one in the X-Silus-Sign header to confirm that it is safe to perform any operations.
There is a difference when encoding data in PHP compared to other languages. PHP escapes slashes, unlike some other languages. Therefore, you may encounter sign a mismatch.
You have to escape slashes with a backslash to make it work properly.
Sign generation example using PHP
<?php
$secretKey = 'uZfQ0GYNurpCjU5iaB9JSOSrBsNTSPGZAnhNRkMO1MNsq2Qep70cyOiLD5jVDiky';
$payload = file_get_contents('php://input');
$payload = json_decode($payload, true);
$time = $_SERVER['HTTP_X_SILUS_TIMESTAMP'];
$requestSign = $_SERVER['HTTP_X_SILUS_SIGN'];
$signData = json_encode($payload, JSON_UNESCAPED_UNICODE) . $time;
$expectedSign = hash_hmac('sha256', $signData, $secretKey);
echo hash_equals($requestSign, $expectedSign) ? 'Yay! Request is valid!' : 'Nah :( Didn\'t work';Sign generation example using NodeJS
const crypto = require('crypto');
const http = require('http');
const secretKey = 'uZfQ0GYNurpCjU5iaB9JSOSrBsNTSPGZAnhNRkMO1MNsq2Qep70cyOiLD5jVDiky';
const server = http.createServer((req, res) => {
let payload = '';
req.on('data', chunk => {
payload += chunk;
});
req.on('end', () => {
const time = req.headers['x-silus-timestamp'];
const requestSign = req.headers['x-silus-sign'];
const signData = payload + time;
const expectedSign = crypto.createHmac('sha256', secretKey).update(signData).digest('hex');
if (crypto.timingSafeEqual(Buffer.from(requestSign), Buffer.from(expectedSign))) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Yay! Request is valid!');
} else {
res.writeHead(400, { 'Content-Type': 'text/plain' });
res.end('Nah :( Didn\'t work');
}
});
});
server.listen(3000, () => {
console.log('Server is listening on port 3000');
});
Last updated