Webhooks

Track purchase status with webhooks.

Links - Webhooks reference


Using webhooks with the Ramp Instant widget doesn't require any setup. You just need to pass a URL which should be called when the purchase changes its status.

Initializing widget with webhook url

const widget = new RampInstantSDK({
  hostAppName: "Your Dapp",
  hostLogoUrl: "https://yourdapp.com/yourlogo.png",
  swapAmount: "1500000000000000000", // 1.5 ETH in wei
  swapAsset: "ETH",
  userAddress: "0xab5801a7d398351b8be11c439e05c5b3259aec9b",
  webhookStatusUrl: "https://my.domain/callback/123/"
});

That's it. You will now receive HTTP POST calls to https://my.domain/callback/123/ from our server, containing a JSON with the purchase data.

Example: listening to webhooks using express.js

Simple express.js application:

const express = require('express'),
    bodyParser = require('body-parser'),
    app = express();

app.use(bodyParser.json());

app.post('/', function(request, response){
  const event = request.body;
  console.log(event);
  const purchase = event.purchase;

  console.log(purchase.id);
  console.log(purchase.fiatValue);
  console.log(purchase.fiatCurrency);

  response.send('OK');
});

app.listen(3000);

Run:

// Run `ngrok http 3000` first to pass internet traffic to this simple application
$ nodejs app.js
{ type: 'CREATED',
  purchase:
   { id: 322,
     endTime: null,
     tokenAddress: null,
     asset: {
       address: null,
       symbol: 'ETH',
       name: 'Ether',
       decimals: 18
     },
     escrowAddress: null,
     cryptoAmount: '30000000000000000',
     ethAmount: '30000000000000000',
     tokenAmount: null,
     fiatCurrency: 'GBP',
     fiatValue: 0.05,
     assetExchangeRate: 1.29518206550279,
     poolFee: 0.01,
     rampFee: 0.000582831929476256,
     purchaseHash: "...",
     purchaseViewToken: "...",
     receiverAddress: "0x2222222222222222222222222222222222222222",
     actions: [ [Object], [Object] ] } }
322
0.05
GBP

Securing webhooks

For your safety we sign our every webhook call with a private ECDSA key.

Before you parse body of the received request check the X-Body-Signature header to verify if the message is signed by Ramp Instant.

To verify the call, you first need to stringify the request body with all its keys in alphabetical order and without any whitespaces. We use fast-json-stable-stringify nodejs module to do that.

You also need our public key

-----BEGIN PUBLIC KEY-----
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAElvxpYOhgdAmI+7oL4mABRAfM5CwLkCbZ
m64ERVKAisSulWFC3oRZom/PeyE2iXPX1ekp9UD1r+51c9TiuIHU4w==
-----END PUBLIC KEY-----

Then, using the header value, prepared message and our public key, you can verify the call.

For example, using nodejs crypto.verify function:

crypto.verify('sha256', Buffer.from(message), rampPublicKey, Buffer.from(signature, 'base64'))

Important:

  • X-Body-Signature is a base64 representation of the signature (in DER format), that's why you have to provide 'base64' as the second argument to Buffer.from() call

  • the message we sign is a stringified request body (JSON) with its keys in the alphabetical order and without whitespaces

  • we use ECDSA key and sha256 hash function to sign the message

See our examples written in nodejs and python for reference.

⇩ click to view example Express app using signature verification ⇩
import { json } from 'body-parser';
import { verify } from 'crypto';
import express from 'express';
import stableStringify from 'fast-json-stable-stringify';
import { readFileSync } from 'fs';


const rampKey = readFileSync('ramp-public.pem').toString();

const app = express();

app.use(json());

app.post('/', (req, res) => {
    if (req.body && req.header('X-Body-Signature')) {        

        const verified = verify(
            'sha256',
            Buffer.from(stableStringify(req.body)),
            rampKey,
            Buffer.from(req.header('X-Body-Signature'), 'base64'),
        );

        if (verified) {
            console.log('SUCCESS');
            res.status(204).send();
        } else {
            console.error('ERROR: Invalid signature');
            res.status(401).send();
        }
    } else {
        console.error('ERROR: Wrong request structure');
        res.status(401).send();
    }
})

app.listen(3000);
⇩ click to view example Flask app using signature verification ⇩
import base64
import hashlib
import json
import pprint

from ecdsa import VerifyingKey
from ecdsa.util import sha256, sigdecode_der
from flask import Flask, request

with open("ramp-public.pem", "rb") as f:
    vk = VerifyingKey.from_pem(f.read())

app = Flask(__name__)


@app.route("/", methods=["POST"])
def verifier_app():

    if not "X-Body-Signature" in request.headers or not reqest.json:
        return "ERROR: Wrong request structure", 401

    sig_base64 = request.headers["X-Body-Signature"]
    sig_bytes = base64.b64decode(sig_base64)

    stringified_message = json.dumps(
        request.json,
        sort_keys=True,        # THESE
        indent=None,           # ARE
        separators=(",", ":")  # IMPORTANT
    )
    message_bytes = stringified_message.encode("utf-8")

    try:
        vk.verify(sig_bytes, message_bytes,
                  hashfunc=sha256, sigdecode=sigdecode_der)
    except:
        return 'ERROR: Invalid signature', 401

    return f"Received a verified message!\n{request.json}"

Testing

For testing we recommend ngrok.

It creates a unique domain that will redirect traffic to your localhost. You don't need to have any HTTP servers running on localhost -- ngrok allows you to inspect all calls to your unique domain on http://localhost:4041/