🔔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
        }
   ]
}
Parameter
Description

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

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.

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