Callbacks receiving
Transaction event payload
Here is an example of a callback payload:
Callback payload example
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"
}
Response schema (application/json):
Parameter | Description |
---|---|
redirectUrl | URL to the provider's purchase page. |
orderId | Internal order ID provided by Fiat API. You can use this field to get information that the transaction status was updated. |
status | Status of transaction. Currently supported transaction statuses are provided below. You can use this field to get information that the transaction status was updated. |
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. |
providerCode | The On-Ramp or Off-Ramp provider code. Possible values. |
currencyFrom | Ticker of the payin currency (in uppercase). |
currencyTo | Ticker of the payout currency (in uppercase). |
amountFrom | Amount of currency the user is going to pay. |
country | Country ISO 3166-1 code (Alpha-2). |
state | State ISO 3166-2 code. |
ip | User's IP address. |
walletAddress | Recipient wallet address. |
walletExtraId | Property required for wallet addresses of currencies that use an additional ID for transaction processing (XRP, XLM, EOS, BNB). |
paymentMethod | The payment method code. Possible values. |
userAgent | User Agent. |
metadata | Metadata object, which can contain any parameters you need:
|
createdAt | Time in ISO 8601 format. |
updatedAt | Time in ISO 8601 format. |
payinAmount | Payin amount. |
payoutAmount | The estimated payout amount. |
payinCurrency | Ticker of the payin currency. |
payoutCurrency | Ticker of the payout currency. |
Supported transaction statuses
Transaction status is passed in the status field on webhook payload. Here is the list of currently supported statuses. Also, this list contains providers' codes for which transactions the statuses return on webhook payload.
Status | Providers sending status | Description |
---|---|---|
created | wert /transak /kado | The transaction has been created but the user has not entered data for KYC and/or did not enter the payment details, e.g. credit card number of bank account. The funds were not sent yet. This status may change to expired or pending. |
pending | moonpay /wert /banxa /transak /kado | The transaction has started. This status covers several stages of the transaction:
|
hold | transak /kado | A verification process is required since the risk-scoring system flagged your transaction. |
refunded | banxa /transak | The provider might not be able to link your transfer to your order when it was received. |
expired | banxa /transak | The user failed to enter all needed data. The transaction has expired and cannot be finished. The user needs to start another transaction in order to purchase crypto for fiat. This is the final status, e.g. it will not change to any other. |
failed | moonpay /wert /banxa /transak /kado | The transaction has failed at any stage. The fiat amount paid will be refunded. The user needs to start another transaction in order to purchase crypto for fiat. This is the final status, e.g. it will not change to any other. |
complete | moonpay /wert /banxa /transak /kado | The transaction completed successfully. Crypto has been transferred to the specified wallet. This is the final status, e.g. it will not change to any other. |
Webhooks signature
To make sure that the incoming requests are originating from a trusted source, you can validate them using our webhook signature. We highly recommend using 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 will allow you to validate that the events have been submitted by Changelly, not a third party.
Before you can verify signatures for webhook events, please notice that all requests must contain the following header:
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. |
There is an example of how to validate your x-callback-signature
parameter in webhooks with Node.js:
Form an object with the orderId
parameter that is sent in the response body in the webhook.
Serialize the generated object in JSON format.
Use the generated string and the public key given by your account manager to verify the signature received from us by checking the SHA256 signature.
There are code examples to validate signature:
- 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.