Set Up Webhooks

Receive real-time notifications when orders are filled, deposits arrive, or withdrawals complete. This guide walks through the full setup from registration to testing.

Step 01

Register a Webhook Endpoint

Call POST /api/v1/webhooks with your HTTPS URL and the events you want to receive. Merx returns a webhook ID and a signing secret.

Create webhookbash
curl -X POST https://merx.exchange/api/v1/webhooks \
  -H "X-API-Key: $MERX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/webhooks/merx",
    "events": ["order.filled", "order.failed", "deposit.received"]
  }'
Responsejson
{
  "id": "wh_abc123",
  "url": "https://example.com/webhooks/merx",
  "events": ["order.filled", "order.failed", "deposit.received"],
  "secret": "whsec_your_signing_secret",
  "status": "active"
}
WARNING

Save the secret immediately. It is only returned once. You need it to verify webhook signatures.

Step 02

Handle Events in Express.js

Use express.raw() to receive the raw body for signature verification. Return 200 immediately and process the event asynchronously.

Express.js handlerjavascript
const express = require('express')
const crypto = require('crypto')

const app = express()
const WEBHOOK_SECRET = process.env.MERX_WEBHOOK_SECRET

app.post(
  '/webhooks/merx',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['x-merx-signature'] || ''
    const expected = crypto
      .createHmac('sha256', WEBHOOK_SECRET)
      .update(req.body)
      .digest('hex')
    const received = signature.replace('sha256=', '')

    if (!crypto.timingSafeEqual(
      Buffer.from(expected, 'hex'),
      Buffer.from(received, 'hex'),
    )) {
      return res.status(401).send('Invalid signature')
    }

    const event = JSON.parse(req.body)
    console.log('Event:', event.event, event.data)

    // Process asynchronously
    res.status(200).send('OK')
  },
)

app.listen(3000)
Step 03

Handle Events in Flask

Flask handlerpython
import hmac
import hashlib
import os
from flask import Flask, request

app = Flask(__name__)
WEBHOOK_SECRET = os.environ["MERX_WEBHOOK_SECRET"]

@app.route("/webhooks/merx", methods=["POST"])
def handle_webhook():
    signature = request.headers.get("X-Merx-Signature", "")
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        request.data,
        hashlib.sha256,
    ).hexdigest()
    received = signature.replace("sha256=", "")

    if not hmac.compare_digest(expected, received):
        return "Invalid signature", 401

    event = request.get_json()
    print("Event:", event["event"], event["data"])

    # Process asynchronously
    return "OK", 200
Step 04

Verify Signatures

Every webhook delivery includes an X-Merx-Signature header with format sha256=<hex_digest>. The digest is computed as HMAC-SHA256 of the raw request body using your webhook secret as the key.

Step 05

Test with curl

Compute a valid signature locally and send a test webhook to your endpoint. This lets you verify your handler works before going live.

Test locallybash
SECRET="whsec_your_signing_secret"
BODY='{"event":"order.filled","data":{"id":"ord_test","status":"FILLED","amount":65000,"total_cost_sun":"5460000"},"timestamp":1711200000}'
SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)

curl -X POST http://localhost:3000/webhooks/merx \
  -H "Content-Type: application/json" \
  -H "X-Merx-Signature: sha256=$SIG" \
  -d "$BODY"
TIP

During development, use a tool like ngrok to expose your local server. Register the ngrok URL as your webhook endpoint to receive real events locally.

Event Reference

EventTrigger
order.filledEnergy order fully filled by providers
order.failedOrder could not be fulfilled
deposit.receivedTRX deposit confirmed on-chain
withdrawal.completedWithdrawal broadcast and confirmed

For full payload examples of each event, see the events reference. For more on signature verification, see the verification guide.