Webhooks
Subscribe to events and MGX will POST a signed payload to your URL when they happen — so you do not have to poll. Requires the webhooks.read / webhooks.write scopes and a token bound to a team.
Create a subscription with the URL you want delivered to and the event types you care about. The SDKs expose the management endpoints (list/create/delete/deliveries) plus a verify() helper for the inbound side.
Create a subscription
const sub = await mgx.webhooks.create({
url: 'https://erp.example.com/mgx/hook',
events: ['trade.created', 'cashbid.offer_received'],
})
// The signing secret is returned only once — store it now.
console.log(sub?.id, sub?.secret)
Events
- Name
bid.accepted- Type
- event
- Description
- A seller accepted your team's bid.
- Name
bid.rejected- Type
- event
- Description
- Your bid was rejected or the lot delisted.
- Name
bid.countered- Type
- event
- Description
- A seller countered your bid.
- Name
trade.created- Type
- event
- Description
- A bid became a trade.
- Name
trade.settled- Type
- event
- Description
- Both invoices on a trade were paid.
- Name
cashbid.offer_received- Type
- event
- Description
- A seller offered against your cash bid.
- Name
inventory.matched- Type
- event
- Description
- A new lot matched a saved search.
Verifying webhooks
Every delivery carries an MGX-Signature header of the form t=<unix>,v1=<hex hmac>, where the HMAC is SHA-256 over the string "{t}.{rawBody}" keyed with the subscription's signing secret — the whsec_... value returned once when the subscription is created. Store it then; MGX cannot show it again.
The SDKs ship a verify() that recomputes the HMAC and compares it in constant time, rejects timestamps outside a tolerance window (300s by default) to block replays, and returns a typed WebhookEvent ({ id, type, created_at, data }). Pass the exact raw request body — not a re-serialized object — or the signature will not match. On any failure it throws, so reject the delivery with a 400 and never act on an unverified payload.
Verify an inbound webhook
// e.g. Express — read the RAW body with express.raw({ type: 'application/json' }).
app.post('/mgx/hook', (req, res) => {
const rawBody = req.body.toString('utf8')
const signature = req.header('MGX-Signature') ?? ''
try {
const event = mgx.webhooks.verify(rawBody, signature, process.env.MGX_WEBHOOK_SECRET!)
switch (event.type) {
case 'trade.created':
console.log('new trade', event.data)
break
case 'cashbid.offer_received':
console.log('offer received', event.data)
break
default:
console.log('unhandled', event.type)
}
res.sendStatus(200)
} catch {
// Bad signature or stale timestamp — do not trust the payload.
res.sendStatus(400)
}
})
The verified WebhookEvent looks like this:
{
"id": "evt_7Qp2",
"type": "bid.accepted",
"created_at": "2026-06-18T15:04:00Z",
"data": { "bid_id": "bid_7Qp2", "reference": "BID-10482", "status": "accepted" }
}
Register a subscription
The signing secret is returned only once, at creation.
Request
curl https://api.mygrainexchange.com/v1/webhooks \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{ "url": "https://erp.example.com/mgx/hook", "events": ["bid.accepted", "trade.settled"] }'
Response
{
"data": {
"id": "whk_4Tz9",
"url": "https://erp.example.com/mgx/hook",
"events": ["bid.accepted", "trade.settled"],
"secret": "whsec_8fK2...shown_once"
}
}
List subscriptions
All webhook subscriptions for your team.
Request
curl https://api.mygrainexchange.com/v1/webhooks \
-H "Authorization: Bearer {token}"
Delete a subscription
Stop delivering to a subscription.
Request
curl -X DELETE https://api.mygrainexchange.com/v1/webhooks/whk_4Tz9 \
-H "Authorization: Bearer {token}"
Inspect deliveries
Recent delivery attempts (status + retry count) for debugging failed deliveries.
Request
curl https://api.mygrainexchange.com/v1/webhooks/whk_4Tz9/deliveries \
-H "Authorization: Bearer {token}"