Webhooks
The platform POSTs a JSON event to your HTTPS endpoint whenever something happens to a client, account, trade or transaction. Every delivery is signed; failed deliveries are retried for a day.
#Set up
In the Octolit CRM: Settings › API & Webhooks › Webhooks › New endpoint. Enter your HTTPS URL, tick the events and optional extra data. Each endpoint has its own signing secret (whsec_ + 40 characters, shown once). Rotate secret creates a new one; both are valid for 24 hours and deliveries carry two v1= signatures meanwhile. Send test event posts webhook.test.
Trade, margin and demo events need the brand switch Trading webhooks. The delivery log in the CRM shows every attempt with status, duration and your response body; failed events can be re-sent by hand.
#Delivery
POST /hooks/octolit HTTP/1.1
Host: your-backend.example
Content-Type: application/json
User-Agent: TradingCRM-Webhooks/1
X-Webhook-Id: evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1
X-Webhook-Event: trade.opened
X-Webhook-Timestamp: 1762853400
X-Webhook-Signature: t=1762853400,v1=5f1c0b8e9d3a…X-Webhook-Id | The event id (evt_…), the same for every retry - use it for idempotency. |
|---|---|
X-Webhook-Event | The event type, same as type in the body. |
X-Webhook-Timestamp | Unix seconds when this attempt was sent. |
X-Webhook-Signature | t=<unix seconds>,v1=<hex> where v1 = HMAC-SHA256(secret, "<t>.<raw body>"). A second ,v1=… appears during secret rotation. |
| Your answer | Any 2xx within 10 seconds counts as delivered. The body is ignored (but logged, 1 KB). Redirects are not followed. |
#Verify the signature
- Take the raw request body bytes (never re-serialised JSON).
- Read
tfrom the signature header; reject if it is more than 300 seconds from now. - Compute HMAC-SHA256 over
t + "." + bodywith your endpoint secret, hex-encoded lowercase. - Compare in constant time with each
v1value; one match is enough. - Answer
200fast; do the work on a queue; ignore an event id you have already processed.
import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';
const app = express();
app.post('/hooks/octolit', express.raw({ type: 'application/json' }), (req, res) => {
const raw = req.body.toString('utf8'); // the raw bytes, not re-serialised JSON
const sig = req.get('X-Webhook-Signature') || '';
const t = /t=(\d+)/.exec(sig)?.[1];
const macs = [...sig.matchAll(/v1=([0-9a-f]+)/g)].map((m) => m[1]);
if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.status(400).end();
const expected = createHmac('sha256', process.env.OCTOLIT_WEBHOOK_SECRET).update(`${t}.${raw}`).digest('hex');
const ok = macs.some((m) => m.length === expected.length && timingSafeEqual(Buffer.from(m), Buffer.from(expected)));
if (!ok) return res.status(401).end();
const event = JSON.parse(raw);
if (seen(event.id)) return res.status(200).end(); // delivered twice: ignore
queue(event); // do the work later
res.status(200).end(); // answer within 10 s
});import hmac, hashlib, os, re, time
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["OCTOLIT_WEBHOOK_SECRET"].encode()
@app.post("/hooks/octolit")
def hook():
raw = request.get_data() # raw bytes
sig = request.headers.get("X-Webhook-Signature", "")
t = re.search(r"t=(\d+)", sig)
macs = re.findall(r"v1=([0-9a-f]+)", sig)
if not t or abs(time.time() - int(t.group(1))) > 300:
abort(400)
expected = hmac.new(SECRET, t.group(1).encode() + b"." + raw, hashlib.sha256).hexdigest()
if not any(hmac.compare_digest(m, expected) for m in macs):
abort(401)
event = request.get_json()
handle_later(event) # idempotent on event["id"]
return "", 200<?php
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
preg_match('/t=(\d+)/', $sig, $t);
preg_match_all('/v1=([0-9a-f]+)/', $sig, $macs);
if (!$t || abs(time() - (int) $t[1]) > 300) { http_response_code(400); exit; }
$expected = hash_hmac('sha256', $t[1] . '.' . $raw, getenv('OCTOLIT_WEBHOOK_SECRET'));
$ok = false;
foreach ($macs[1] as $m) { if (hash_equals($expected, $m)) { $ok = true; } }
if (!$ok) { http_response_code(401); exit; }
$event = json_decode($raw, true);
handleLater($event); // idempotent on $event['id']
http_response_code(200);app.MapPost("/hooks/octolit", async (HttpRequest req) =>
{
using var reader = new StreamReader(req.Body);
var raw = await reader.ReadToEndAsync();
var sig = req.Headers["X-Webhook-Signature"].ToString();
var t = Regex.Match(sig, @"t=(\d+)").Groups[1].Value;
var macs = Regex.Matches(sig, @"v1=([0-9a-f]+)").Select(m => m.Groups[1].Value);
if (t == "" || Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(t)) > 300) return Results.BadRequest();
var key = Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("OCTOLIT_WEBHOOK_SECRET")!);
var expected = Convert.ToHexString(HMACSHA256.HashData(key, Encoding.UTF8.GetBytes($"{t}.{raw}"))).ToLowerInvariant();
if (!macs.Any(m => CryptographicOperations.FixedTimeEquals(Encoding.ASCII.GetBytes(m), Encoding.ASCII.GetBytes(expected)))) return Results.Unauthorized();
var evt = JsonSerializer.Deserialize<JsonElement>(raw);
await queue.EnqueueAsync(evt); // idempotent on evt.id
return Results.Ok();
});@PostMapping("/hooks/octolit")
public ResponseEntity<Void> hook(@RequestBody byte[] raw, @RequestHeader("X-Webhook-Signature") String sig) throws Exception {
Matcher t = Pattern.compile("t=(\\d+)").matcher(sig);
if (!t.find() || Math.abs(Instant.now().getEpochSecond() - Long.parseLong(t.group(1))) > 300) return ResponseEntity.badRequest().build();
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(System.getenv("OCTOLIT_WEBHOOK_SECRET").getBytes(UTF_8), "HmacSHA256"));
mac.update((t.group(1) + ".").getBytes(UTF_8));
String expected = HexFormat.of().formatHex(mac.doFinal(raw));
Matcher v = Pattern.compile("v1=([0-9a-f]+)").matcher(sig);
boolean ok = false;
while (v.find()) ok |= MessageDigest.isEqual(v.group(1).getBytes(UTF_8), expected.getBytes(UTF_8));
if (!ok) return ResponseEntity.status(401).build();
queue.add(new String(raw, UTF_8)); // idempotent on event id
return ResponseEntity.ok().build();
}func hook(w http.ResponseWriter, r *http.Request) {
raw, _ := io.ReadAll(r.Body)
sig := r.Header.Get("X-Webhook-Signature")
t := regexp.MustCompile(`t=(\d+)`).FindStringSubmatch(sig)
macs := regexp.MustCompile(`v1=([0-9a-f]+)`).FindAllStringSubmatch(sig, -1)
if t == nil { w.WriteHeader(400); return }
ts, _ := strconv.ParseInt(t[1], 10, 64)
if math.Abs(float64(time.Now().Unix()-ts)) > 300 { w.WriteHeader(400); return }
h := hmac.New(sha256.New, []byte(os.Getenv("OCTOLIT_WEBHOOK_SECRET")))
h.Write([]byte(t[1] + "."))
h.Write(raw)
expected := hex.EncodeToString(h.Sum(nil))
ok := false
for _, m := range macs { ok = ok || hmac.Equal([]byte(m[1]), []byte(expected)) }
if !ok { w.WriteHeader(401); return }
queue <- raw // idempotent on the event id
w.WriteHeader(200)
} Paste a real delivery into the webhook verifier to check your implementation.
#Retries
A non-2xx answer, a timeout or a connection error schedules the next attempt. Each delay gets 0-10 % jitter.
| Attempt | After the previous one |
|---|---|
| 1 | 1 minute |
| 2 | 5 minutes |
| 3 | 30 minutes |
| 4 | 1 hour |
| 5 | 2 hours |
| 6 | 6 hours |
| 7 | 14 hours |
| 8 | final (about 24 h after the first try) |
id and compare created_at before overwriting state.
#The envelope
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "client.updated",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"…": "…"
}
}evt_... - the same on every retry and resend.
#Events
17 event types. Click one in the sidebar or scroll. Each shows the data object and a complete example.
#A client was created
data
The CRM's client. email / phone are masked unless the key carries clients:read_email / clients:read_phone.
Child fields (24)
Child fields (2)
Child fields (2)
Child fields (2)
Your client id.
Item fields (2)
Only with clients:read_ftd.
Only with clients:read_comments.
Only with clients:read_custom_fields.
client.updated only.
client.agent_changed only.
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "client.created",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"first_name": "Ana",
"last_name": "Ortiz",
"full_name": "Ana Ortiz",
"email": "ana.ortiz@example.com",
"emails": [
"ana.ortiz@example.com"
],
"phone": "+357 99 123456",
"phones": [
"+357 99 123456"
],
"brand": {
"id": 1042,
"name": "Main"
},
"desk": {
"id": 1042,
"name": "Main"
},
"sales_status": "string",
"sales_status_changed_at": "2026-11-11T09:30:00Z",
"kyc": {
"id_status": "string",
"billing_status": "string"
},
"active": true,
"is_lead": true,
"deleted": true,
"country": "CY",
"external_user_id": "abc123",
"agents": [
{
"id": 1042,
"name": "Main"
}
],
"created_at": "2026-11-11T09:30:00.000000Z",
"updated_at": "2026-11-11T09:31:12.000000Z",
"first_deposit_date": "2026-11-11T09:30:00Z",
"comments": [
null
],
"custom_fields": {}
},
"changed_fields": [
"string"
],
"previous_agents": [
null
]
}
}#A client's tracked fields changed (`data.changed_fields`)
data
The CRM's client. email / phone are masked unless the key carries clients:read_email / clients:read_phone.
Child fields (24)
Child fields (2)
Child fields (2)
Child fields (2)
Your client id.
Item fields (2)
Only with clients:read_ftd.
Only with clients:read_comments.
Only with clients:read_custom_fields.
client.updated only.
client.agent_changed only.
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "client.updated",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"first_name": "Ana",
"last_name": "Ortiz",
"full_name": "Ana Ortiz",
"email": "ana.ortiz@example.com",
"emails": [
"ana.ortiz@example.com"
],
"phone": "+357 99 123456",
"phones": [
"+357 99 123456"
],
"brand": {
"id": 1042,
"name": "Main"
},
"desk": {
"id": 1042,
"name": "Main"
},
"sales_status": "string",
"sales_status_changed_at": "2026-11-11T09:30:00Z",
"kyc": {
"id_status": "string",
"billing_status": "string"
},
"active": true,
"is_lead": true,
"deleted": true,
"country": "CY",
"external_user_id": "abc123",
"agents": [
{
"id": 1042,
"name": "Main"
}
],
"created_at": "2026-11-11T09:30:00.000000Z",
"updated_at": "2026-11-11T09:31:12.000000Z",
"first_deposit_date": "2026-11-11T09:30:00Z",
"comments": [
null
],
"custom_fields": {}
},
"changed_fields": [
"string"
],
"previous_agents": [
null
]
}
}#The assigned agents changed (`data.previous_agents`)
data
The CRM's client. email / phone are masked unless the key carries clients:read_email / clients:read_phone.
Child fields (24)
Child fields (2)
Child fields (2)
Child fields (2)
Your client id.
Item fields (2)
Only with clients:read_ftd.
Only with clients:read_comments.
Only with clients:read_custom_fields.
client.updated only.
client.agent_changed only.
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "client.agent_changed",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"first_name": "Ana",
"last_name": "Ortiz",
"full_name": "Ana Ortiz",
"email": "ana.ortiz@example.com",
"emails": [
"ana.ortiz@example.com"
],
"phone": "+357 99 123456",
"phones": [
"+357 99 123456"
],
"brand": {
"id": 1042,
"name": "Main"
},
"desk": {
"id": 1042,
"name": "Main"
},
"sales_status": "string",
"sales_status_changed_at": "2026-11-11T09:30:00Z",
"kyc": {
"id_status": "string",
"billing_status": "string"
},
"active": true,
"is_lead": true,
"deleted": true,
"country": "CY",
"external_user_id": "abc123",
"agents": [
{
"id": 1042,
"name": "Main"
}
],
"created_at": "2026-11-11T09:30:00.000000Z",
"updated_at": "2026-11-11T09:31:12.000000Z",
"first_deposit_date": "2026-11-11T09:30:00Z",
"comments": [
null
],
"custom_fields": {}
},
"changed_fields": [
"string"
],
"previous_agents": [
null
]
}
}#A client was deleted (`data.client` = id + deleted_at only)
data
The CRM's client. email / phone are masked unless the key carries clients:read_email / clients:read_phone.
Child fields (24)
Child fields (2)
Child fields (2)
Child fields (2)
Your client id.
Item fields (2)
Only with clients:read_ftd.
Only with clients:read_comments.
Only with clients:read_custom_fields.
client.updated only.
client.agent_changed only.
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "client.deleted",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"first_name": "Ana",
"last_name": "Ortiz",
"full_name": "Ana Ortiz",
"email": "ana.ortiz@example.com",
"emails": [
"ana.ortiz@example.com"
],
"phone": "+357 99 123456",
"phones": [
"+357 99 123456"
],
"brand": {
"id": 1042,
"name": "Main"
},
"desk": {
"id": 1042,
"name": "Main"
},
"sales_status": "string",
"sales_status_changed_at": "2026-11-11T09:30:00Z",
"kyc": {
"id_status": "string",
"billing_status": "string"
},
"active": true,
"is_lead": true,
"deleted": true,
"country": "CY",
"external_user_id": "abc123",
"agents": [
{
"id": 1042,
"name": "Main"
}
],
"created_at": "2026-11-11T09:30:00.000000Z",
"updated_at": "2026-11-11T09:31:12.000000Z",
"first_deposit_date": "2026-11-11T09:30:00Z",
"comments": [
null
],
"custom_fields": {}
},
"changed_fields": [
"string"
],
"previous_agents": [
null
]
}
}#A client signed in (password, link or single sign-on)
data
Child fields (3)
Your client id (external_user_id).
Child fields (2)
Child fields (6)
password, a mailed sign-in link, your SSO token / the partner API, or a CRM agent signing in as the client.
passwordlinkssoagentThe second sign-in step (code) was passed.
traderdashboardnullBrowser / system from the user agent, e.g. "Chrome / Windows".
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "client.login",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"external_id": "cust_10042",
"brand": {
"id": 1042,
"name": "Main"
}
},
"login": {
"method": "password",
"two_factor": false,
"app": "trader",
"ip": "203.0.113.42",
"device": "iPhone",
"at": "2026-11-11T09:30:00.000000Z"
}
}
}#A client has not signed in for N days (the brand's "inactive after" setting)
data
Child fields (3)
Your client id (external_user_id).
Child fields (2)
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "client.inactive",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"external_id": "cust_10042",
"brand": {
"id": 1042,
"name": "Main"
}
},
"last_login_at": "2026-10-01T12:00:00Z",
"inactive_days": 30
}
}#A trading account was opened
data
Child fields (3)
Your client id (external_user_id).
Child fields (2)
Child fields (15)
Child fields (3)
internal or mt.
MT accounts only.
client
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "account.created",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"external_id": "cust_10042",
"brand": {
"id": 1042,
"name": "Main"
}
},
"account": {
"id": 1042,
"name": "Main",
"currency": "USD",
"demo": false,
"type": {
"id": 1042,
"name": "Main",
"provider": "string"
},
"balance": "10250.00",
"equity": "10312.40",
"margin": "108.45",
"free_margin": "10203.95",
"margin_level": "9509.3",
"open_pl": "string",
"closed_pl": "string",
"credit": "string",
"mt_login": "string",
"created_at": "2026-11-11T09:30:00.000000Z"
},
"created_by": "string"
}
}#A position was opened
data
Child fields (3)
Your client id (external_user_id).
Child fields (2)
Child fields (22)
openpendingclosedbuysellMargin used (account currency).
Position value (account currency).
trade.modified only.
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "trade.opened",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"external_id": "cust_10042",
"brand": {
"id": 1042,
"name": "Main"
}
},
"position": {
"id": 1042,
"account_id": 55,
"symbol": "EURUSD",
"status": "open",
"side": "buy",
"order_type": 1,
"amount": "100.00",
"trade_amount": "string",
"units": 10000,
"lot_size": 1.5,
"leverage": 100,
"open_price": "string",
"stop_loss": "string",
"take_profit": "string",
"profit": "19.00",
"commission": "0.00",
"swap": "-0.12",
"market": "string",
"opened_at": "2026-11-11T09:30:00Z",
"closed_at": "2026-11-11T10:05:12Z",
"closed_by": "string",
"created_at": "2026-11-11T09:30:00.000000Z"
},
"changed_fields": [
"string"
]
}
}#A position's stop loss / take profit / amount changed
data
Child fields (3)
Your client id (external_user_id).
Child fields (2)
Child fields (22)
openpendingclosedbuysellMargin used (account currency).
Position value (account currency).
trade.modified only.
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "trade.modified",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"external_id": "cust_10042",
"brand": {
"id": 1042,
"name": "Main"
}
},
"position": {
"id": 1042,
"account_id": 55,
"symbol": "EURUSD",
"status": "open",
"side": "buy",
"order_type": 1,
"amount": "100.00",
"trade_amount": "string",
"units": 10000,
"lot_size": 1.5,
"leverage": 100,
"open_price": "string",
"stop_loss": "string",
"take_profit": "string",
"profit": "19.00",
"commission": "0.00",
"swap": "-0.12",
"market": "string",
"opened_at": "2026-11-11T09:30:00Z",
"closed_at": "2026-11-11T10:05:12Z",
"closed_by": "string",
"created_at": "2026-11-11T09:30:00.000000Z"
},
"changed_fields": [
"string"
]
}
}#A position was closed (`data.position.closed_by` = user, tp, sl, stop_out, admin, ...)
data
Child fields (3)
Your client id (external_user_id).
Child fields (2)
Child fields (22)
openpendingclosedbuysellMargin used (account currency).
Position value (account currency).
trade.modified only.
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "trade.closed",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"external_id": "cust_10042",
"brand": {
"id": 1042,
"name": "Main"
}
},
"position": {
"id": 1042,
"account_id": 55,
"symbol": "EURUSD",
"status": "open",
"side": "buy",
"order_type": 1,
"amount": "100.00",
"trade_amount": "string",
"units": 10000,
"lot_size": 1.5,
"leverage": 100,
"open_price": "string",
"stop_loss": "string",
"take_profit": "string",
"profit": "19.00",
"commission": "0.00",
"swap": "-0.12",
"market": "string",
"opened_at": "2026-11-11T09:30:00Z",
"closed_at": "2026-11-11T10:05:12Z",
"closed_by": "string",
"created_at": "2026-11-11T09:30:00.000000Z"
},
"changed_fields": [
"string"
]
}
}#The client's first trade on a demo account
data
Child fields (3)
Your client id (external_user_id).
Child fields (2)
Child fields (22)
openpendingclosedbuysellMargin used (account currency).
Position value (account currency).
trade.modified only.
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "demo.first_trade",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"external_id": "cust_10042",
"brand": {
"id": 1042,
"name": "Main"
}
},
"position": {
"id": 1042,
"account_id": 55,
"symbol": "EURUSD",
"status": "open",
"side": "buy",
"order_type": 1,
"amount": "100.00",
"trade_amount": "string",
"units": 10000,
"lot_size": 1.5,
"leverage": 100,
"open_price": "string",
"stop_loss": "string",
"take_profit": "string",
"profit": "19.00",
"commission": "0.00",
"swap": "-0.12",
"market": "string",
"opened_at": "2026-11-11T09:30:00Z",
"closed_at": "2026-11-11T10:05:12Z",
"closed_by": "string",
"created_at": "2026-11-11T09:30:00.000000Z"
},
"changed_fields": [
"string"
]
}
}#The client's margin level fell to the brand's margin call level
data
Child fields (3)
Your client id (external_user_id).
Child fields (2)
The account's margin level in percent when the event was raised.
The brand's margin call level in percent (CRM Settings > API & Webhooks > Broker login, default 100). margin.call is sent once when the account goes under it and again every hour while it stays under; stop.out when positions were closed by the margin rule.
stop.out only.
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "margin.call",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"external_id": "cust_10042",
"brand": {
"id": 1042,
"name": "Main"
}
},
"account_id": 55,
"margin_level": "9509.3",
"level": 100,
"equity": "10312.40",
"margin": "108.45",
"closed_positions": [
1
]
}
}#Positions were closed by the stop-out rule
data
Child fields (3)
Your client id (external_user_id).
Child fields (2)
The account's margin level in percent when the event was raised.
The brand's margin call level in percent (CRM Settings > API & Webhooks > Broker login, default 100). margin.call is sent once when the account goes under it and again every hour while it stays under; stop.out when positions were closed by the margin rule.
stop.out only.
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "stop.out",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"external_id": "cust_10042",
"brand": {
"id": 1042,
"name": "Main"
}
},
"account_id": 55,
"margin_level": "9509.3",
"level": 100,
"equity": "10312.40",
"margin": "108.45",
"closed_positions": [
1
]
}
}#A client's price alert fired (the level was crossed)
data
Child fields (3)
Your client id (external_user_id).
Child fields (2)
Child fields (9)
The trading account the alert's price type belongs to.
The pair as the trader shows it (EUR/USD).
bidaskThe side of the level the alert waited for.
abovebelowThe alert level.
The price that crossed it.
The client's own note on the alert.
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "price_alert.triggered",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"external_id": "cust_10042",
"brand": {
"id": 1042,
"name": "Main"
}
},
"alert": {
"id": 1042,
"account_id": 55,
"symbol": "EURUSD",
"price_kind": "bid",
"direction": "above",
"price": "string",
"triggered_price": "string",
"triggered_at": "2026-11-11T09:30:00Z",
"note": "Card deposit"
}
}
}#A deposit changed status (pending, approved, declined, cancelled)
data
Child fields (3)
Your client id (external_user_id).
Child fields (2)
Child fields (9)
depositwithdrawalpendingapproveddeclinedcancelledrefunded{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "deposit.status",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"external_id": "cust_10042",
"brand": {
"id": 1042,
"name": "Main"
}
},
"transaction": {
"id": 1042,
"kind": "deposit",
"status": "pending",
"previous_status": "pending",
"amount": "100.00",
"currency": "USD",
"account_id": 55,
"method": "card",
"at": "2026-11-11T09:30:00.000000Z"
}
}
}#A withdrawal changed status (pending, approved, declined, cancelled)
data
Child fields (3)
Your client id (external_user_id).
Child fields (2)
Child fields (9)
depositwithdrawalpendingapproveddeclinedcancelledrefunded{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "withdrawal.status",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {
"client": {
"id": 1042,
"external_id": "cust_10042",
"brand": {
"id": 1042,
"name": "Main"
}
},
"transaction": {
"id": 1042,
"kind": "deposit",
"status": "pending",
"previous_status": "pending",
"amount": "100.00",
"currency": "USD",
"account_id": 55,
"method": "card",
"at": "2026-11-11T09:30:00.000000Z"
}
}
}#The "Send test event" button of the Octolit CRM
{
"id": "evt_01JCX7R0M6YQ3A8ZK5R4T2N9P1",
"type": "webhook.test",
"created_at": "2026-11-11T09:30:00.000000Z",
"company_id": 10,
"data": {}
}