Introduction
Webhooks are how Coinpes tells your server that something happened for a trader you referred. A signup, a CFD fill, a settled contract, or a commission movement arrives as an HTTPS POST — you do not poll the Partner API for that data.
The Partner WebSocket is different. It streams one authorized user's quotes and positions into your app. Webhooks are partner-wide: they fire for referred traders even if they never opened your product.
When to use webhooks
Use them when your backend needs to react without a person sitting in Trade. Typical jobs:
- Credit a lead in your CRM when
user.registeredarrives. - Update a trader dashboard when
order.filledorcontract.settledfires. - Book revenue when
commission.createdorpayout.completedarrives.
You register URLs in the Partner Dashboard, not through the API. There is no Partner API route to create, list, or rotate endpoints.
Add an endpoint
- Enroll as a partner, then open Referrals.
- Scroll to Webhooks. Enter an HTTPS URL your server can receive (localhost will not work unless you expose it with a public tunnel).
- Tick the events you actually handle. Start with
user.registered,order.filled, andcontract.settledif you are unsure. - Copy the signing secret immediately. It is shown once at creation. Store it in server env vars, never in a frontend bundle.
Each endpoint only receives the events you selected. If you later need another type, add a new endpoint or recreate this one with the extra events ticked.
What arrives on the wire
Coinpes POSTs JSON. The body is an envelope around the event-specific payload:
{
"id": "evt_test_preview",
"type": "order.filled",
"created_at": "2026-08-30T06:00:00.000Z",
"data": {
"userId": 10042,
"accountId": 3,
"orderId": 9101,
"symbol": "R_100",
"side": "BUY",
"quantity": "1.00",
"price": "1234.56"
}
}id— unique event id. Use it as your idempotency key.type— the event name, for exampleorder.filled.created_at— ISO timestamp.data— fields for that event (user id, order id, amounts as strings, and so on).
Headers include Content-Type: application/json, X-Event-Id (same as id), and X-Signature in the form sha256=<hex>. Deliveries time out after 10 seconds. Return 2xx before you do slow work.
Event catalog
user.registered— a trader signed up through your attribution.order.filled,position.opened,position.closed— CFD / Market activity on a referred account.contract.placed,contract.settled— timed contracts.account.updated— wallet snapshot after a change.commission.created,commission.approved,commission.reversed,payout.completed— your partner earnings, not the trader's P/L.
Demo trades still emit trading events if you subscribed, but they never create commission. Filter on account type in data if you only want live activity.
Verify every delivery
Anyone who discovers your URL can POST JSON at it. Treat the body as untrusted until the signature matches. HMAC-SHA256 the raw body bytes with your signing secret, then compare to the hex after sha256= in constant time.
Read the body as a buffer, not a pre-parsed object — JSON re-serialization changes the bytes and the HMAC will fail.
import crypto from 'node:crypto'
function verifyWebhook(rawBody, secret, signatureHeader) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
const received = signatureHeader.replace(/^sha256=/, '')
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))
}Example inbound handler
Compare the recomputed digest to the header. Reject with 401 if they differ. Then parse JSON and skip events whose id you already stored.
app.post('/webhooks/coinpes', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-signature']
if (!verifyWebhook(req.body.toString(), process.env.COINPES_WEBHOOK_SECRET, signature)) {
return res.sendStatus(401)
}
const event = JSON.parse(req.body.toString())
// handle order.filled, contract.settled, etc.
res.sendStatus(200)
})Test from the dashboard
You do not need a referred trader to fill an order before you know the handler works. On each webhook row, choose Test. That opens a modal with:
- An event select — only types this endpoint is subscribed to (for example
order.filled). - A JSON editor prefilled with a sample envelope (
id,type,created_at,data). Editdatato match what your handler expects. - Send test — Coinpes POSTs to your URL with a real HMAC, the same headers as a live event, and a 10 second timeout.
The modal reports delivery status and the HTTP status your server returned. The attempt also appears in that endpoint's delivery log, so you can compare a test against later live events.
Point the URL at a public HTTPS tunnel in development (ngrok, Cloudflare Tunnel, or similar). A test to http://localhost cannot reach your laptop from Coinpes's servers.
What a good test proves
A 2xx from Test means TLS, routing, signature verification, and JSON parsing all succeeded. It does not mean you credited the right user in production — keep live events idempotent by id so a second delivery cannot double-apply.
Failed deliveries
Expand a webhook to open its delivery log. You will see status, HTTP code, and attempts. Non-2xx responses and timeouts are retried. After several failed attempts the delivery is marked failed.
Timeouts look like failures even if your process later finished the work. That is why the handler should acknowledge first (2xx) and enqueue the job. If Coinpes retries, your id check must no-op the second time.
Webhook CRUD is dashboard-only. Use Test on the same row to send a signed sample before you wait for a real referral to trade.
Signature sample, diagrams, and the full event list: Webhooks reference.
