Сallback handling
After the merchant's client has made a transaction to the wallet with the required amount, the transaction is processed by the processing side and a callback is sent to the merchant's backend.
swaggerReceiving Callback
Request
XAMAX processing sends a callback
POST https://exmpale.com/callback HTTP/1.1
Content-Length: 397
Cache-Control: no-cache
Authorization Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjMxN2NkNjA3MmE1ZDgxYWQwMjlmOTYzMzc0MTMxNWM5ZDczNTZiNWM1NzM0YTE5YmE0MDQ5MmQ1ODY1MGY5ZGQiLCJzaWdfYWxnIjoicGtjczF2MTUiLCJ0eXAiOiJKV1QifQ.eyJib2R5X2hhc2giOiJlY2ZiNDBmMTFkNDBlOTRmYzViMzNlZDM2YTljYTM0MjcwOTkzM2Y3ZTFlMDhlNTUxNDNlZTc3MmJiNzg0NmFiIiwiYm9keV9oYXNoX21ldGhvZCI6InNoYTI1NiIsImlzcyI6InhhbWF4LmlvIiwic3ViIjoicHJvY2Vzc2luZyIsImF1ZCI6WyJib2JAZXhhbXBsZS5jb20iXSwiZXhwIjoxNzA5MDMzMjEyLCJuYmYiOjE3MDkwMzI5MTMsImlhdCI6MTcwOTAzMjkxMiwianRpIjoiOTMzMTI1ODgtNmZkMC00OTI4LWIxNjItOWExOWMxODQ5OGU0In0.j77ChxeVNVfPpB5xAM-6olQTA52I6klv_KEAIRgJaUrqOC3vaHEqHEwB06bcgdEtUJKTSoWD0Ce74nYaFdF8yt2kk5zaafnF7s2PExJWfxwEv4Frz3X2xJXYSB1XypSeEJNeaVyvcwzWQYmAUuClNV50UvTEJH8VBgjGC668Vrw6ZV6Zx6GA5gb2lOwdIC9damm_0L0V1g6ww2DHPq68ag4r6stYWwoELRFl9dHil2XyqjNpmHd2RTnObrNEXn_D-rv-eQCObay_HwjMWsXjBYOsICsTZcqsQJbjFdu91GL158qWM5-FOuy3aAKm3gWertfHNt37mbmrngYaYZ6h8w
X-Resource-Type: incoming-transaction
User-Agent: xamax.io callback/1
Content-Type: application/json
Accept-Encoding: gzip
{"txId":2027,"walletAddress":"TBMczkFmXEzfpmQEghqFiVtss2fqsqSfhL","status":"transaction_status_confirmed","expiredAt":"2024-02-15T09:59:08Z","amountRequired":"26001000","amount":"26001000","code":"usdt_trc20","txHash":"f501644a6597a3b04194ace5d7af7a1de4bfb30624de9b6b4a87938f5b1e0401","confirmations":45,"exchangeRate":{"currency":"usd","exchange_rate":1,"currency_amount":26,"code":"usdt_trc20"}}
Parameter | Description |
---|---|
txId | The identifier that was specified when creating the invoice |
walletAddress | The address of the wallet to which the transaction came |
code | Currency code |
amountRequired | The amount with which the client must make a transaction to the wallet |
expiredAt | The date the transaction will expire |
status | Status |
txHash | Transaction hash on the blockchain |
amount | Received transaction amount |
confirmations | The number of confirmations in the blockchain |
Transaction statuses
The main statuses for which we produce a callback
Status | Value | Description |
---|---|---|
Confirmed | transaction_status_confirmed | Transaction confirmed in blockchain |
Failed | transaction_status_failed | Transaction failed in blockchain. In case of problems on the part of the blockchain, the transaction from waiting can go to failed. |
Cancelled | transaction_status_canceled | Transaction cancelled in processing. The cancelled status is not used, since it is necessary to catch the transaction before it goes to the blockchain, and this is a fairly short period of time. so not used yet. *reserved |
Dust | transaction_status_dust | Transaction incoming amount is small. |
Refunded | transaction_status_refunded | Transaction refunded |
Сallback handling. Step-by-step
After receiving callback request you should handle all parameters
1. Get a header from callback request
Example of Authorization
header
Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImZkY2IxYTdiZmY5OGNhMDkyOWY5MDBjNmU2ZmYxYzBkNGI4NmIzZDllMWRjMjM5OGU2NjlmYTRkYTA0ZTA3YTAiLCJzaWdfYWxnIjoicGtjczF2MTUiLCJ0eXAiOiJKV1QifQ.eyJib2R5X2hhc2giOiJmMTE1MmU2YjFhOWY1YmQ0ODhiZDk5MWFkMTgxNTYwNDM0ZmFmZDA5ZmRiNGMyOTFlMDM1MWQ0ODlkNzUxYzU2IiwiYm9keV9oYXNoX21ldGhvZCI6InNoYTI1NiIsImlzcyI6InhhbWF4LmlvIiwic3ViIjoicHJvY2Vzc2luZyIsImF1ZCI6WyJib2JAZXhhbXBsZS5jb20iXSwiZXhwIjoxNjcxNzQ0NTAxLCJuYmYiOjE2NzE3NDQyMDIsImlhdCI6MTY3MTc0NDIwMSwianRpIjoiYTJiZWRkMTUtNmJhNS00MmIzLThjZjItODk2ZWNiMDA2MWNhIn0.EOic90GWQEWPQEWkNi8pqf10ZAT3EMcWf7V-b7XqgG93TRH_HedQF3wisuZ5vY-OySuGcaROaTyWxiDWLFJ-ILNpzzqxCq8xuH8p8SlgQeLpYv7jh3DQyjQuMnWKEARJRN8QoeYqLE1jO1As7-3QqJIDuvb6sPo1C89VIVW1FqYwtPk8x2VLd0TeUk3Z18fD1YLqvc5Q2a8DWW_SsNOBCftAKk9YtBr1YCDpF_AxkE337Sb6YIW8_XCAEbYq8eCSw7DSrMfEMrWWnJJCGuvCFLEHv6KTrTg2mQ0Fppvbj3o1I6f_uDzQ90FfuZxaEN-iFuP5TH6SpO6PXPmrPZXWHA
2. Download JWKS public keys from our endpoint
A JWT session is transmitted, which can be confirmed using public JWKS keys.
You can get public keys by /.well-known/jwks.json
request. You should use the key that has the same kid
as in Authorization
header
Keys are changed every 7 days
- Production
- Sandbox
GET https://api.xamax.io/.well-known/jwks.json HTTP/1.1
GET https://api.sandbox.xamax.io/.well-known/jwks.json HTTP/1.1
3. Parse JWT token by library
Libraries for Token Signing/Verification
// Parsed JWT header
{
"alg": "RS256",
"kid": "fdcb1a7bff98ca0929f900c6e6ff1c0d4b86b3d9e1dc2398e669fa4da04e07a0",
"sig_alg": "pkcs1v15",
"typ": "JWT"
}
// Parsed JWT payload
{
"body_hash": "f1152e6b1a9f5bd488bd991ad181560434fafd09fdb4c291e0351d489d751c56",
"body_hash_method": "sha256",
"iss": "xamax.io",
"sub": "processing",
"aud": [
"bob@example.com"
],
"exp": 1671744501,
"nbf": 1671744202,
"iat": 1671744201,
"jti": "a2bedd15-6ba5-42b3-8cf2-896ecb0061ca"
}
4. Check JWT expiration date and "aud" field
Checking the expiration time of the JWT token
// Parsed JWT payload
{
"body_hash": "f1152e6b1a9f5bd488bd991ad181560434fafd09fdb4c291e0351d489d751c56",
"body_hash_method": "sha256",
"iss": "xamax.io",
"sub": "processing",
"aud": [
"bob@example.com" // your email
],
"exp": 1671744501,
"nbf": 1671744202,
"iat": 1671744201,
"jti": "a2bedd15-6ba5-42b3-8cf2-896ecb0061ca"
}
5. Check kid and validate signature
Check kid in parsed JWT header and in JWKS key list
1
2
3
4
5
6
7
// JWT Header
{
"alg": "RS256",
"kid": "fdcb1a7bff98ca0929f900c6e6ff1c0d4b86b3d9e1dc2398e669fa4da04e07a0",
"sig_alg": "pkcs1v15",
"typ": "JWT"
}
1
2
3
4
5
6
7
// JWKS keys
{
"keys": [
{
"kid": "fdcb1a7bff98ca0929f900c6e6ff1c0d4b86b3d9e1dc2398e669fa4da04e07a0"
"e": "AQAB",
"kty": "RSA"
Checking the validity of the signature based on public keys. Keys are rotated regularly, so you should not cache them for a long time. Ready-made libraries support the functionality when they update keys in the cache if the corresponding [kid] is not found (https://www.rfc-editor.org/rfc/rfc7517#section-4.5)
Validate JWT signature by JWKS keys. PHP example
$jwsVerifier = new JWSVerifier(new AlgorithmManager([new RS256()]));
$isVerified = $jwsVerifier->verifyWithKeySet($jws, $jwk, 0);
if (!$isVerified) {
throw new Exception("invalid signature");
}
7. Comparing the hash of the request body with the hash in the body of the JWT session
Request example
POST https://exmpale.com/callback HTTP/1.1
Content-Length: 397
Cache-Control: no-cache
Authorization Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjMxN2NkNjA3MmE1ZDgxYWQwMjlmOTYzMzc0MTMxNWM5ZDczNTZiNWM1NzM0YTE5YmE0MDQ5MmQ1ODY1MGY5ZGQiLCJzaWdfYWxnIjoicGtjczF2MTUiLCJ0eXAiOiJKV1QifQ.eyJib2R5X2hhc2giOiJlY2ZiNDBmMTFkNDBlOTRmYzViMzNlZDM2YTljYTM0MjcwOTkzM2Y3ZTFlMDhlNTUxNDNlZTc3MmJiNzg0NmFiIiwiYm9keV9oYXNoX21ldGhvZCI6InNoYTI1NiIsImlzcyI6InhhbWF4LmlvIiwic3ViIjoicHJvY2Vzc2luZyIsImF1ZCI6WyJib2JAZXhhbXBsZS5jb20iXSwiZXhwIjoxNzA5MDMzMjEyLCJuYmYiOjE3MDkwMzI5MTMsImlhdCI6MTcwOTAzMjkxMiwianRpIjoiOTMzMTI1ODgtNmZkMC00OTI4LWIxNjItOWExOWMxODQ5OGU0In0.j77ChxeVNVfPpB5xAM-6olQTA52I6klv_KEAIRgJaUrqOC3vaHEqHEwB06bcgdEtUJKTSoWD0Ce74nYaFdF8yt2kk5zaafnF7s2PExJWfxwEv4Frz3X2xJXYSB1XypSeEJNeaVyvcwzWQYmAUuClNV50UvTEJH8VBgjGC668Vrw6ZV6Zx6GA5gb2lOwdIC9damm_0L0V1g6ww2DHPq68ag4r6stYWwoELRFl9dHil2XyqjNpmHd2RTnObrNEXn_D-rv-eQCObay_HwjMWsXjBYOsICsTZcqsQJbjFdu91GL158qWM5-FOuy3aAKm3gWertfHNt37mbmrngYaYZ6h8w
X-Resource-Type: incoming-transaction
User-Agent: xamax.io callback/1
Content-Type: application/json
Accept-Encoding: gzip
{"txId":2027,"walletAddress":"TBMczkFmXEzfpmQEghqFiVtss2fqsqSfhL","status":"transaction_status_confirmed","expiredAt":"2024-02-15T09:59:08Z","amountRequired":"26001000","amount":"26001000","code":"usdt_trc20","txHash":"f501644a6597a3b04194ace5d7af7a1de4bfb30624de9b6b4a87938f5b1e0401","confirmations":45,"exchangeRate":{"currency":"usd","exchange_rate":1,"currency_amount":26,"code":"usdt_trc20"}}
Comparing the hash of the request body with the hash in the body of the JWT session. Key body_hash
, hash type specified in body_hash_method
. Before getting the hash of the request body, it is not necessary to parse it, sort the keys, and any other operations.
Step-by-Step:
Parse JWT payload and get body_hash and body_hash_method
// Parsed JWT payload from request example
{
"body_hash": "ecfb40f11d40e94fc5b33ed36a9ca342709933f7e1e08e55143ee772bb7846ab",
"body_hash_method": "sha256",
"iss": "xamax.io",
"sub": "processing",
"aud": [
"bob@example.com"
],
"exp": 1709033212,
"nbf": 1709032913,
"iat": 1709032912,
"jti": "93312588-6fd0-4928-b162-9a19c18498e4"
}
Take hash sha256 from body request
Parse body request like string without json parsing
sha256('{"txId":2027,"walletAddress":"TBMczkFmXEzfpmQEghqFiVtss2fqsqSfhL","status":"transaction_status_confirmed","expiredAt":"2024-02-15T09:59:08Z","amountRequired":"26001000","amount":"26001000","code":"usdt_trc20","txHash":"f501644a6597a3b04194ace5d7af7a1de4bfb30624de9b6b4a87938f5b1e0401","confirmations":45,"exchangeRate":{"currency":"usd","exchange_rate":1,"currency_amount":26,"code":"usdt_trc20"}}')
result: ecfb40f11d40e94fc5b33ed36a9ca342709933f7e1e08e55143ee772bb7846ab
And compare body_hash
from JWT payload with sha256
result. If all checks are passed, then the callback can be considered valid and the data that was passed in the request body can be trusted.
Code example
A simple example of request header and body validation
- Go
- PHP
- Ruby
package main
import (
"crypto/sha256"
"fmt"
"log"
"github.com/MicahParks/keyfunc"
"github.com/golang-jwt/jwt/v4"
)
type Claims struct {
BodyHash string `json:"body_hash"`
BodyHashMethod string `json:"body_hash_method"`
jwt.RegisteredClaims
}
func main() {
var (
err error
jwks *keyfunc.JWKS
jwksUrlPath = "https://api.sandbox.xamax.io/.well-known/jwks.json"
)
// load jwks by URL
if jwks, err = keyfunc.Get(jwksUrlPath, keyfunc.Options{RefreshUnknownKID: true}); err != nil {
log.Fatalf("failed load JWKS: %s", err)
}
var (
// request header
header = `eyJhbGciOiJSUzI1NiIsImtpZCI6ImZkY2IxYTdiZmY5OGNhMDkyOWY5MDBjNmU2ZmYxYzBkNGI4NmIzZDllMWRjMjM5OGU2NjlmYTRkYTA0ZTA3YTAiLCJzaWdfYWxnIjoicGtjczF2MTUiLCJ0eXAiOiJKV1QifQ.eyJib2R5X2hhc2giOiJmMTE1MmU2YjFhOWY1YmQ0ODhiZDk5MWFkMTgxNTYwNDM0ZmFmZDA5ZmRiNGMyOTFlMDM1MWQ0ODlkNzUxYzU2IiwiYm9keV9oYXNoX21ldGhvZCI6InNoYTI1NiIsImlzcyI6InhhbWF4LmlvIiwic3ViIjoicHJvY2Vzc2luZyIsImF1ZCI6WyJib2JAZXhhbXBsZS5jb20iXSwiZXhwIjoxNjcxNzQ0NTAxLCJuYmYiOjE2NzE3NDQyMDIsImlhdCI6MTY3MTc0NDIwMSwianRpIjoiYTJiZWRkMTUtNmJhNS00MmIzLThjZjItODk2ZWNiMDA2MWNhIn0.EOic90GWQEWPQEWkNi8pqf10ZAT3EMcWf7V-b7XqgG93TRH_HedQF3wisuZ5vY-OySuGcaROaTyWxiDWLFJ-ILNpzzqxCq8xuH8p8SlgQeLpYv7jh3DQyjQuMnWKEARJRN8QoeYqLE1jO1As7-3QqJIDuvb6sPo1C89VIVW1FqYwtPk8x2VLd0TeUk3Z18fD1YLqvc5Q2a8DWW_SsNOBCftAKk9YtBr1YCDpF_AxkE337Sb6YIW8_XCAEbYq8eCSw7DSrMfEMrWWnJJCGuvCFLEHv6KTrTg2mQ0Fppvbj3o1I6f_uDzQ90FfuZxaEN-iFuP5TH6SpO6PXPmrPZXWHA`
// request body
data = `{"txId":400,"walletAddress":"0x2C5FE81093b9eA33B4017BfB3c646C1e1daCC2dD","status":"transaction_status_expired","expiredAt":"2022-12-21T18:47:27Z","amountRequired":"20000000","amount":"0","code":"usdt"}`
token *jwt.Token
claims Claims
)
// parse the JWT with claims
if token, err = jwt.ParseWithClaims(header, &claims, jwks.Keyfunc); err != nil {
log.Fatalf("Failed to parse JWT: %s", err)
}
// check JWT validation
if !token.Valid {
log.Fatal("invalid token")
}
// check merchant account email
if claims.Audience[0] != "bob@example.com" {
log.Fatal("incorrect merchant email")
}
// generate checksum from raw request body
var checksum = fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
// compare checksum from jwt claims with generated body checksum
if claims.BodyHash != checksum {
log.Fatal("incorrect body hash")
}
// callback is valid
log.Println("ok")
}
Add dependency by composer
$ composer require web-token/jwt-core=^3.1 web-token/jwt-framework=^3.1 web-token/jwt-checker
$ composer dump-autoload
Code
<?php
require_once "vendor/autoload.php";
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\JWKSet;
use Jose\Component\Signature\Algorithm\RS256;
use Jose\Component\Signature\Serializer\CompactSerializer;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\Checker\ClaimCheckerManager;
use Jose\Component\Checker;
// JWKS from:
// production https://api.xamax.io/.well-known/jwks.json
// sandbox https://api.sandbox.xamax.io/.well-known/jwks.json
//
// !! Important !!
// Service rotate jwks every 7 days!
//
$jwk =
JWKSet::createFromJson('{"keys":[{"n":"r1D0UFEAL0b_lyrc8_-U4Iq_d1XurSF6B6TkXUjsgCWUVcsfgno4ZirGHmLbYyeZh4Gt31F4rioenJXCZmST0SwlBEmGBOlvk_81EDIEvraWjbVoWYeZbLHdG2hBO_dMh6d2InsK_ZaJt1UvmRf-hYiqXJ95V6FVOyGv2LEQWiXhRI2S8zfk15bYCa8iTJaMIk0BH8CMRkpKyInXHs9Cnb-aqtK0NmIlMQpqMt0KI46KCc5sBr29yxRVwCZkER2h4G55EHMFyTMKiXU_aC2yOmESAA843xNO8OSTbkJ7gpd3ZDYr1mfXQcsA0zaKS_fz6yLFiqlEBfP_fjdZP9G4Vw","e":"AQAB","kty":"RSA","use":"sig","kid":"a9cbfef4360e610735823a7839d5f8407a8f0ba17fba8ca584359df987e074a6"},{"n":"vGiU5d6cfk_QezucZfL7Ve_XOLSZr0ysFbU5uUYdn7DI_TJkWr2qzIAaR_dc8hLgBdvvaHVPjGJNzOiP-uMHG51ES_3BKg-XuSJCapRl9oAh_4mTc0k37yp0CI_JDz6C2CkHu8hrIWOggoZPhdK2qlQDRio3aFYabQ2n8qBLxIPa9jd6AcBTEXLQ8yUed-0CAOSpgu-FBHRTohzW00Aw42jhMl8Dbaf96oPclEv0hosyoeZo8K6AvIBCOJxugWOzC9d4yN9FUKA63YXS6j4HFtSxhm_s_XPy5VTXM2fu4rThD-fpFrGNAOl-DZOayQ7XGzl4MCVrVZFVLYcY__Jc7w","e":"AQAB","kty":"RSA","use":"sig","kid":"fdcb1a7bff98ca0929f900c6e6ff1c0d4b86b3d9e1dc2398e669fa4da04e07a0"}]}');
$requestAuthorizationHeader =
'eyJhbGciOiJSUzI1NiIsImtpZCI6ImE5Y2JmZWY0MzYwZTYxMDczNTgyM2E3ODM5ZDVmODQwN2E4ZjBiYTE3ZmJhOGNhNTg0MzU5ZGY5ODdlMDc0YTYiLCJzaWdfYWxnIjoicGtjczF2MTUiLCJ0eXAiOiJKV1QifQ.eyJib2R5X2hhc2giOiJhM2YwNjA0ZTIxY2FiNzlhNzY3Yzk4ZWUwODBkMTU4YzZmYTc1YjZiZTQ2MmY4ZTAyMGRiZTA4MWM4Y2RmYTBjIiwiYm9keV9oYXNoX21ldGhvZCI6InNoYTI1NiIsImlzcyI6InhhbWF4LmlvIiwic3ViIjoicHJvY2Vzc2luZyIsImF1ZCI6WyJib2JAZXhhbXBsZS5jb20iXSwiZXhwIjoxNjc4NDYwNDc1LCJuYmYiOjE2Nzg0NjAxNzYsImlhdCI6MTY3ODQ2MDE3NSwianRpIjoiZDY4ZjMzMjItNzY1ZC00MzQ0LWJlMjUtOTk3OGE1NWYwN2IzIn0.YjX08SLqtc0zCRG0h9TYCY0N7MPQgIiV8s0MhN-TdxfFlr6D-DQJRfpYdkJHlch5d7qhPG7RS35uv7TnXxpp1VzDMdjDfKq7ivKnxEYHJ5kexnnTu2OdJBvDec8rTtRJ7S31lIegvFWVqR6L925QrZHybbs3lfo-yP2CJ876udgSYOwwGxvutCLQlPIadJU-ntbzQoQXHwmY22BPNUkkoMMLFlSq2jDt932RJV2nl09cWTvidRmHlwmlKtUO8L6Q9362Kanl2-llUtKj4egQzFiFCHhYRQbLYXkWXCg9iw92BzfstdU-OocBxtW0l3CeFz352CBG7cgdg63nOu4RBg';
$requestBody =
'{"walletAddress":"0x0000000000000000000000000000000000000000","status":"transaction_status_confirmed","expiredAt":"2023-03-10T15:11:15.579915618Z","amountRequired":"100000000000","amount":"100000000000","code":"eth","txHash":"0x0000000000000000000000000000000000000000000000000000000000000000","confirmations":5}';
$serializerManager = new CompactSerializer();
$jws = $serializerManager->unserialize($requestAuthorizationHeader);
// initialize claim checker
$claimCheckerManager = new ClaimCheckerManager(
[
new Checker\IssuedAtChecker(),
new Checker\NotBeforeChecker(),
new Checker\ExpirationTimeChecker(), // in current example this checker always fall, because token already expired
new Checker\IssuerChecker(["xamax.io"]),
new Checker\AudienceChecker('bob@example.com'), // merchant email
]
);
$claims = json_decode($jws->getPayload(), true);
// check JWT claims
$claimCheckerManager->check($claims);
// check body hash
if ($claims['body_hash'] != hash($claims['body_hash_method'], $requestBody)) {
throw new Exception("incorrect body checksum");
}
// checking available key
$ok = false;
$headers = $jws->getSignature(0)->getProtectedHeader();
foreach ($jwk->all() as $k) {
if ($k->get('kid') == $headers['kid']) {
$ok = true;
break;
}
}
// jwks keys outdated
if (!$ok) {
// !! Important !!
// If kid not found, required update jwks set from production/sandbox
}
// check jwt signature
$jwsVerifier = new JWSVerifier(new AlgorithmManager([new RS256()]));
$isVerified = $jwsVerifier->verifyWithKeySet($jws, $jwk, 0);
if (!$isVerified) {
throw new Exception("invalid signature");
}
echo "OK";
Add dependency by gem
$ gem install jwt --user-install
Code
require 'jwt'
require 'digest'
# JWKS from:
# production https://api.xamax.io/.well-known/jwks.json
# sandbox https://api.sandbox.xamax.io/.well-known/jwks.json
#
# !! Important !!
# Service rotate jwks every 7 days!
jwks_hash = { "keys": [{ "n": "r1D0UFEAL0b_lyrc8_-U4Iq_d1XurSF6B6TkXUjsgCWUVcsfgno4ZirGHmLbYyeZh4Gt31F4rioenJXCZmST0SwlBEmGBOlvk_81EDIEvraWjbVoWYeZbLHdG2hBO_dMh6d2InsK_ZaJt1UvmRf-hYiqXJ95V6FVOyGv2LEQWiXhRI2S8zfk15bYCa8iTJaMIk0BH8CMRkpKyInXHs9Cnb-aqtK0NmIlMQpqMt0KI46KCc5sBr29yxRVwCZkER2h4G55EHMFyTMKiXU_aC2yOmESAA843xNO8OSTbkJ7gpd3ZDYr1mfXQcsA0zaKS_fz6yLFiqlEBfP_fjdZP9G4Vw", "e": "AQAB", "kty": "RSA", "use": "sig", "kid": "a9cbfef4360e610735823a7839d5f8407a8f0ba17fba8ca584359df987e074a6" }, { "n": "vGiU5d6cfk_QezucZfL7Ve_XOLSZr0ysFbU5uUYdn7DI_TJkWr2qzIAaR_dc8hLgBdvvaHVPjGJNzOiP-uMHG51ES_3BKg-XuSJCapRl9oAh_4mTc0k37yp0CI_JDz6C2CkHu8hrIWOggoZPhdK2qlQDRio3aFYabQ2n8qBLxIPa9jd6AcBTEXLQ8yUed-0CAOSpgu-FBHRTohzW00Aw42jhMl8Dbaf96oPclEv0hosyoeZo8K6AvIBCOJxugWOzC9d4yN9FUKA63YXS6j4HFtSxhm_s_XPy5VTXM2fu4rThD-fpFrGNAOl-DZOayQ7XGzl4MCVrVZFVLYcY__Jc7w", "e": "AQAB", "kty": "RSA", "use": "sig", "kid": "fdcb1a7bff98ca0929f900c6e6ff1c0d4b86b3d9e1dc2398e669fa4da04e07a0" }] }
jwks = JWT::JWK::Set.new(jwks_hash)
# token from Authorization header
token = 'eyJhbGciOiJSUzI1NiIsImtpZCI6ImE5Y2JmZWY0MzYwZTYxMDczNTgyM2E3ODM5ZDVmODQwN2E4ZjBiYTE3ZmJhOGNhNTg0MzU5ZGY5ODdlMDc0YTYiLCJzaWdfYWxnIjoicGtjczF2MTUiLCJ0eXAiOiJKV1QifQ.eyJib2R5X2hhc2giOiJhM2YwNjA0ZTIxY2FiNzlhNzY3Yzk4ZWUwODBkMTU4YzZmYTc1YjZiZTQ2MmY4ZTAyMGRiZTA4MWM4Y2RmYTBjIiwiYm9keV9oYXNoX21ldGhvZCI6InNoYTI1NiIsImlzcyI6InhhbWF4LmlvIiwic3ViIjoicHJvY2Vzc2luZyIsImF1ZCI6WyJib2JAZXhhbXBsZS5jb20iXSwiZXhwIjoxNjc4NDYwNDc1LCJuYmYiOjE2Nzg0NjAxNzYsImlhdCI6MTY3ODQ2MDE3NSwianRpIjoiZDY4ZjMzMjItNzY1ZC00MzQ0LWJlMjUtOTk3OGE1NWYwN2IzIn0.YjX08SLqtc0zCRG0h9TYCY0N7MPQgIiV8s0MhN-TdxfFlr6D-DQJRfpYdkJHlch5d7qhPG7RS35uv7TnXxpp1VzDMdjDfKq7ivKnxEYHJ5kexnnTu2OdJBvDec8rTtRJ7S31lIegvFWVqR6L925QrZHybbs3lfo-yP2CJ876udgSYOwwGxvutCLQlPIadJU-ntbzQoQXHwmY22BPNUkkoMMLFlSq2jDt932RJV2nl09cWTvidRmHlwmlKtUO8L6Q9362Kanl2-llUtKj4egQzFiFCHhYRQbLYXkWXCg9iw92BzfstdU-OocBxtW0l3CeFz352CBG7cgdg63nOu4RBg'
# Request body string
# !! Important !!
# don't change this string for prevent sha256 checking errors
# decode to hash and encode to hash will change this string. don't do it
# how to get raw request body in rails: https://stackoverflow.com/a/14306478/1626348
body = '{"walletAddress":"0x0000000000000000000000000000000000000000","status":"transaction_status_confirmed","expiredAt":"2023-03-10T15:11:15.579915618Z","amountRequired":"100000000000","amount":"100000000000","code":"eth","txHash":"0x0000000000000000000000000000000000000000000000000000000000000000","confirmations":5}'
# verify token
decoded_token = JWT.decode token, nil, true, {
algorithm: 'RS256',
# !! Important !!
# in this example this argument is false for valid example
# in production you must set verify_expiration: true
verify_expiration: false,
verify_aud: true,
# merchant account email
aud: "bob@example.com",
verify_iss: true,
iss: 'xamax.io',
jwks: jwks,
}
# get hash from jwt payload
jwtBodyHash = decoded_token[0]['body_hash']
# compare sha256 hash of raw body with hash in payload
if Digest::SHA256.hexdigest(body) != jwtBodyHash
# invalid hash
raise ArgumentError.new("jwt hash doesn't match")
end
# callback is valid
print "ok"