Receive callbacks
In this article, you can learn how to receive callbacks in Fiat API.
Webhooks signature
To make sure that the incoming requests are from a trusted source, validate them with our webhook signature. We highly recommend you use this practice for security reasons.
Changelly signs webhook events sent to you. To use webhooks signature, we will generate public and private API keys. Your account manager will send you a public key to verify the signature in webhooks.
Each event includes a signature which is done using the signature header. This header includes orderId signed by the private key. This allow you to validate that Changelly submitted the events, not a third party.
Before you can verify signatures for webhook events, pay attetion that all requests must contain the following headers.
| Header | Description |
|---|---|
| x-callback-api-key | Your public API key to verify the signature in webhooks. |
| x-callback-signature | The serialized string with an orderId signed by our private key according to the RSA-SHA256 method. |
General flow of validation x-callback-signature:
- Form an object with the
orderIdparameter sent in the webhook response body. - Serialize the generated object in JSON format.
- Use the generated string and the public key from your account manager to verify the signature received from us by checking the SHA256 signature.
For more details, learn the following sample codes.
- Express.js
- PHP
import crypto from 'crypto';
import express from 'express';
const CALLBACK_API_KEY = '<Your API key>';
const CALLBACK_PUBLIC_KEY = '<Your public callback key>';
function _validateSignature(signature, payload) {
const publicKeyObject = crypto.createPublicKey({
key: CALLBACK_PUBLIC_KEY,
type: 'pkcs1',
format: 'pem',
encoding: 'base64',
});
const payloadBuffer = Buffer.from(payload);
const signatureBuffer = Buffer.from(signature, 'base64');
return crypto.verify('sha256', payloadBuffer, publicKeyObject, signatureBuffer);
}
const app = express();
app.use(express.json());
app.post('/callback', (req, res) => {
const payload = req.body;
const apiKey = req.headers['x-callback-api-key'];
const signature = req.headers['x-callback-signature'];
if (apiKey !== CALLBACK_API_KEY) {
return res.status(400).send({status: 'error'});
}
const signaturePayload = JSON.stringify({orderId: payload.orderId});
if (!_validateSignature(signature, signaturePayload)) {
return res.status(400).send({status: 'error'});
}
console.log('Callback payload: ', payload);
return res.status(200).send({status: 'success'});
});
app.listen(4200, () => {
console.log('Server listening on port http://localhost:4200');
});
<?php
use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Crypt\RSA;
const CALLBACK_API_KEY = '<Your API key>';
const CALLBACK_PUBLIC_KEY = '<Your public callback key>';
function isValidSignature(string $signature, array $payload): bool
{
$key = PublicKeyLoader::load(base64_decode(self::API_CALLBACK_PUBLIC_KEY))->withPadding(RSA::ENCRYPTION_PKCS1);
$publicKey = openssl_pkey_get_public($key);
$signature = base64_decode($signature);
$signaturePayload = json_encode(['orderId' => $payload['orderId']]);
return openssl_verify($signaturePayload, $signature, $publicKey, OPENSSL_ALGO_SHA256);
}
?>
For reading PKCS1 key you must use phpseclib.
Transaction event payload
Response schema:
| Parameter | Description |
|---|---|
| amountFrom | Amount of currency the user is going to pay. |
| сountry | Country ISO 3166-1 code (Alpha-2). |
| state | State ISO 3166-2 code. |
| createdAt | Time in ISO 8601 format. |
| updatedAt | Time in ISO 8601 format. |
| currencyFrom | Ticker of the payin currency (in uppercase). |
| currencyTo | Ticker of the payout currency (in uppercase). |
| externalUserId | User ID provided by you. |
| externalOrderId | Order ID provided by you. You can use this field to get information that the transaction status was updated. |
| ip | User's IP address. |
| metadata | Metadata object, which can contain any parameters you need:
|
| orderId | Internal order ID provided by Fiat API. You can use this field to get information that the transaction status was updated. |
| payinAmount | Payin amount. |
| payoutAmount | The estimated payout amount. |
| payinCurrency | Ticker of the payin currency. |
| payoutCurrency | Ticker of the payout currency. |
| paymentMethod | The payment method code. Possible values. |
| providerCode | The On-Ramp or Off-Ramp provider code. Possible values. |
| redirectUrl | URL to the provider's purchase page. |
| status | Transaction status. Possible values. You can use this field to get information that the transaction status was updated. |
| transactionHash | Transaction hash in the blockchain. |
| userAgent | User Agent. |
| walletAddress | Recipient wallet address. |
| walletExtraId | Property required for wallet addresses of currencies that use an additional ID for transaction processing (XRP, XLM, EOS, BNB). |
Sample callback payload
Header: x-callback-signature
Body:
{
"redirectUrl": "https://buy.moonpay.com/?CurrencyCode=eth&baseCurrencyCode=usd¤cyCode=eth&baseCurrencyAmount=150&externalTransactionId=71ahw34&walletAddress=0x8cfbd31371e9bec8c82ae101e25bd9394c03a227",
"orderId": "5154302e-3stl-75p4",
"status": "pending",
"externalUserId": "122hd",
"externalOrderId": "71ahw34",
"providerCode": "moonpay",
"currencyFrom": "USD",
"currencyTo": "ETH",
"amountFrom": "150",
"country": "EE",
"state": null,
"ip": null,
"walletAddress": "0x8cfbd31371e9bec8c82ae101e25bd9394c03a227",
"walletExtraId": null,
"paymentMethod": "card",
"userAgent": null,
"metadata": null,
"createdAt": "2019-07-22T10:10:09.000",
"payinAmount": "150",
"payoutAmount": "0.0756",
"payinCurrency": "USD",
"payoutCurrency": "ETH",
"transactionHash": "f418********************************38530e9831e9e16"
}
Supported transaction statuses
Webhook payload passes a transaction status in the status field. Learn more details about supported transaction statuses.
Pay attention that some providers do not support certain transaction statuses.