Request Signing
MoonPay will sign all requests sent to our partners that include the following.
- signature - HEX hmac request signature
- timestamp - UNIX time in seconds of when the signature has been generated.
The parameters mentioned above will be present in the request headers:
- X-SIGNATURE-V2
- X-TIMESTAMP
How to validate the signature?
Consider the following example:
https://api.moonpay.com/v3/nft/asset_info/0x2953399124f0cbb46d2cbacd8a89cf0599974963/1?listingId=19
X-SIGNATURE-V2: ecb073e45654b47f3edf0c805e3d7d4f3069d3d63ca4e83b8c3f97b2cd914009
X-TIMESTAMP:1645556506
- Check if the timestamp is valid (within 30s from now).
- Generate signature using your secret key. To do that you will need:
- secret key:
sk_test_key
- endpoint URI, note that the base URLis excluded, but all query parameters should be included:
/asset_info/0x2953399124f0cbb46d2cbacd8a89cf0599974963/1?listingId=19
- HTTP request method in upper case:
GET
- timestamp:
1645556506
- stringified bodyif the request method is
POST
,PUT
orPATCH
.
We usehmac
withsha256
digested to hex to generate a signature.
- secret key:
- Compare signature from the request with the one you just generated (remember to use safe method of comparison likecrypto.timingSafeEqual in nodejs or hmac.compare_digest in Python).
Query parameters in endpoint requests
When making requests to your partner endpoints, MoonPay will send your query parameters in alphabetical order.
Be aware that we occasionally add new query parameters to the requests we make to your endpoints, and that certain cloud providers and their API gateway may change the order of our parameters resulting in a failed signature validation. When implementing these endpoints you should account for these query parameters, as using a pre-defined set of query parameters may result in signature validation issues.
Example with TypeScript/NodeJS
import crypto from 'crypto';
const SIGNATURE_VALID_FOR = 30;
function getTimestamp(): number {
return Math.round(new Date().getTime() / 1000);
}
function validateTimestamp(timestamp: number): boolean {
return getTimestamp() - timestamp <= SIGNATURE_VALID_FOR;
}
function generateSignature(
secretKey: string, // your secret key e.g. sk_test_6CDFl9eVtnRpiJsKu6tHD3jye9AW2u
uri: string, // do not include your base URL
method: string, // HTTP request method in upper case
timestamp: number, // timestamp is a number
body?: string, // do not include for GET requests, make sure it's stringified
): string {
// order has to be the same
let payload = `${method};${uri};${timestamp}`;
if (body) {
payload += `;${body}`;
}
return crypto
.createHmac('sha256', secretKey)
.update(payload)
.digest('hex');
}
function verifySignature(
signature: string,
secretKey: string,
uri: string,
method: string,
timestamp: number,
body?: string,
): boolean {
const generatedSignature = generateSignature(
secretKey,
uri,
method,
timestamp,
body
);
if (
!crypto.timingSafeEqual(
Buffer.from(generatedSignature, 'hex'),
Buffer.from(signature, 'hex'),
)
) {
return false;
}
if (!validateTimestamp(timestamp)) {
return false;
}
return true;
}
Updated about 1 year ago