Skip to main content

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&currencyCode=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):

ParameterDescription
redirectUrlURL to the provider's purchase page.
orderIdInternal order ID provided by Fiat API. You can use this field to get information that the transaction status was updated.
statusStatus of transaction. Currently supported transaction statuses are provided below. You can use this field to get information that the transaction status was updated.
externalUserIdUser ID provided by you.
externalOrderIdOrder ID provided by you. You can use this field to get information that the transaction status was updated.
providerCodeThe On-Ramp or Off-Ramp provider code. Possible values.
currencyFromTicker of the payin currency (in uppercase).
currencyToTicker of the payout currency (in uppercase).
amountFromAmount of currency the user is going to pay.
countryCountry ISO 3166-1 code (Alpha-2).
stateState ISO 3166-2 code.
ipUser's IP address.
walletAddressRecipient wallet address.
walletExtraIdProperty required for wallet addresses of currencies that use an additional ID for transaction processing (XRP, XLM, EOS, BNB).
paymentMethodThe payment method code. Possible values.
userAgentUser Agent.
metadataMetadata object, which can contain any parameters you need:
  • If you don't provide the metadata object in the request, null will be returned in metadata in response.
  • If you specify an empty object in the request, an empty object will be returned in the response.
createdAtTime in ISO 8601 format.
updatedAtTime in ISO 8601 format.
payinAmountPayin amount.
payoutAmountThe estimated payout amount.
payinCurrencyTicker of the payin currency.
payoutCurrencyTicker 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.

StatusProviders sending statusDescription
createdwert/transak/kadoThe 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.
pendingmoonpay/wert/banxa/transak/kadoThe transaction has started. This status covers several stages of the transaction:
  • KYC process.
  • fiat transfer after successful KYC.
  • crypto transfer to the specified wallet.
This status may change to either failed or completed.
holdtransak/kadoA verification process is required since the risk-scoring system flagged your transaction.
refundedbanxa/transakThe provider might not be able to link your transfer to your order when it was received.
expiredbanxa/transakThe 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.
failedmoonpay/wert/banxa/transak/kadoThe 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.
completemoonpay/wert/banxa/transak/kadoThe 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:

HeaderDescription
x-callback-api-keyYour public API key to verify the signature in webhooks.
x-callback-signatureThe 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:

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');
});