Orders
Create and manage exchange orders. Status semantics and flow diagrams live in Order Lifecycle; authentication rules in Authentication.
Create Order
POST /orders/
Request
curl -X POST "https://api.n.exchange/en/api/v2/orders/" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "x-referral-token: YOUR_REFERRAL_TOKEN" \
-d '{
"deposit_amount": "500.00",
"deposit_currency": "USDTERC",
"withdraw_currency": "BTC",
"withdraw_address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"rate_id": "abc123-000000"
}'
Headers: Authorization required; x-referral-token recommended for attribution; X-User-Timezone optional — see Authentication.
Request fields:
| Field | Type | Required | Description |
|---|---|---|---|
deposit_currency | string or object | Yes | Currency you send. Three formats — see currency identification |
withdraw_currency | string or object | Yes | Currency you receive. Same three formats |
withdraw_address | string | Yes | Destination address. Must be non-blank and valid for the withdrawal currency's blockchain. Validation is format-level (regex/checksum per currency) — it cannot tell apart chains sharing a format (e.g. two EVM networks), so verify the address/chain pairing client-side |
deposit_amount | string | Conditional | Amount you send (max 8 decimals). Non-DeFi orders require deposit_amount or withdraw_amount; DeFi floating/fixed orders may omit both |
withdraw_amount | string | Conditional | Amount you receive (max 8 decimals). Same conditionality |
side | string | No | BUY (default) or SELL. Crypto swaps use BUY (just omit the field); SELL is for fiat flows. DeFi orders are always BUY. Treat side in responses as informational — don't use it to classify fiat vs crypto |
withdraw_address_extra_id | string | No | Memo/destination tag for the withdrawal (max 127 chars) |
refund_address | string | No* | Refund address — always denominated on the deposit currency's blockchain (refunds return where funds came from). Required for DeFi orders when the destination (withdraw) chain is non-EVM — currently Solana, Tron, Bitcoin |
refund_address_extra_id | string | No | Memo/destination tag for refund transactions (max 127 chars) |
rate_id | string | No | Quote id from the same GET /rate/ row. Binds order validation to that quote snapshot. Invalid/expired values fail validation |
max_slippage_percent | string/null | No | Write-only adverse-price-movement threshold for floating orders ("1.5" = 1.5%). Pattern: ^-?\d{0,3}(?:\.\d{0,4})?$. Cannot be combined with rate_id; see Slippage protection |
is_defi | boolean | No | true = DeFi order (requires DeFi-enabled pair/currencies, min deposit $20 USD equivalent) |
is_fixed_rate | boolean | No | DeFi only: fixed-rate (RFQ) mode. Must not be combined with is_limit_order |
is_limit_order | boolean | No | DeFi only: limit order; requires limit_receive_amount |
limit_receive_amount | string | No | Limit-order minimum receive amount in human-readable destination units |
limit_ttl | integer | No | Limit-order TTL in seconds. Omitted/non-positive → backend default; positive values must meet the backend minimum |
markup | integer | No | Dynamic partner fee in basis points (0–10000). Pre-agreed capability; mutually exclusive with referral_rate (both → 400) |
referral_rate | string | No | Deprecated — use markup. Decimal fraction, e.g. "0.005" |
referral_code | string | No | CeFi referral code (≤20 chars, ^[A-Za-z0-9_-]+$). Must belong to the API key owner |
Slippage protection for floating orders
max_slippage_percent sets the adverse price movement you accept between creating an order and processing its deposit. For example, "1.5" means 1.5%. On a CeFi floating order, if the market moves against the order by at least that percentage before the deposit is processed, the order is refunded instead of being filled at the worse rate.
| Order mode | Behavior |
|---|---|
| CeFi floating | Applies the requested threshold, subject to the validation and referral-code rules below |
CeFi with rate_id | Rejected — remove either rate_id or max_slippage_percent |
| DeFi floating | Applies under DeFi's own floor and cap, replacing the configured default |
| DeFi limit | Accepted but has no effect; the limit order's minimum received amount controls execution |
| DeFi fixed-rate | Rejected |
- The value must be greater than
0and at most100, with up to four decimal places.0, negative values, and values below the configured minimum return400. - If the value exceeds the referral code's slippage cap, it is clamped to that cap. If the referral code's cap is itself below the configured minimum, the request value is ignored. The field can therefore add protection to the referral code's setting, but cannot reduce that protection.
- Omit the field to use the referral code's setting alone.
- The field is write-only and is never returned in an order response, because echoing it could disclose the referral code's cap.
Example — CeFi floating order with 1.5% slippage protection:
{
"deposit_amount": "500.00",
"deposit_currency": "USDC",
"withdraw_currency": "BTC",
"withdraw_address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"refund_address": "0xYourUsersSourceAddress",
"max_slippage_percent": "1.5"
}
Example — object currency format with quote binding:
{
"deposit_amount": "500.00",
"deposit_currency": { "code": "USDT", "network": "ETH" },
"withdraw_currency": { "code": "BTC", "network": "BTC" },
"withdraw_address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"refund_address": "0xYourUsersSourceAddress",
"rate_id": "waq1T39kPFn9LOETpzTnX6by7Yc-001ABB"
}
Response: 201 Created or 200 Existing Order
| Status | Meaning |
|---|---|
201 | New order created. This is the default for every create request, CeFi and DeFi alike |
200 | DeFi, legacy integrations only: an existing matching open order was returned instead of a new one |
By default, every POST /orders/ creates a new order with its own unique_reference, for CeFi and DeFi alike (the one exception is the legacy DeFi reuse described below). Create the order once, when the user confirms the swap, and keep the unique_reference you get back. To show a rate before that, use GET /rate/, not an order.
Do not blindly re-send a create after a client-side timeout. First check whether the first attempt succeeded, for example via GET /orders/ filtered by pair and status=INITIAL, or gate creation behind your own idempotency layer.
Floating-rate DeFi orders created with the same parameters share one deposit address (fixed-rate orders each get their own). A deposit to that address is matched to the oldest open order for those parameters, so an order created by a retry can expire unpaid while an earlier one completes.
A few integrations that predate this behaviour still receive 200 with the existing INITIAL order when a DeFi create matches one of their open orders (same pair, withdraw_address, is_fixed_rate/is_limit_order, refund_address and referral). It is not available to new integrations. Treat 200 and 201 bodies identically.
Response fields (OrderV2):
| Field | Type | Description |
|---|---|---|
unique_reference | string | Order identifier for tracking (^[A-Za-z0-9_-]+$) |
created_on | string | ISO 8601 creation timestamp |
side | string | BUY or SELL (read-only) |
status | string | Order status string — canonical values in Order Lifecycle |
deposit_currency / withdraw_currency | string | Always flat string codes in responses (e.g. "USDCARB"), regardless of the request format |
deposit_amount / withdraw_amount | string | Amounts (8 decimals); may be null on DeFi orders until execution |
deposit_address | string | Where you send funds |
deposit_address_extra_id | string | Memo/tag that must accompany the deposit, when required |
withdraw_address / withdraw_address_extra_id | string | Destination you provided |
refund_address / refund_address_extra_id | string | Refund destination you provided |
payment_window_minutes | integer | Deposit deadline window in minutes (see note below) |
fixed_rate_deadline | string/null | UTC timestamp until which the fixed rate holds |
rate | string | Exchange rate applied to the order |
deposit_transaction / withdraw_transaction | string | Transaction hashes, filled as they appear |
refund_transaction | string | Transaction ID of the refund. Empty until refunded funds have been sent back on-chain |
is_defi / is_fixed_rate / is_limit_order | boolean | Mode flags |
limit_expires_at | string/null | Limit-order expiry (limit orders) |
limit_min_received | string/null | Limit-order minimum, in destination smallest units as a string integer |
applied_referral_rate | string | Dynamic partner fee applied (only when a dynamic fee was requested) |
A null fixed_rate_deadline marks a floating-rate order: no rate deadline; the rate is determined when the deposit arrives. CeFi floating orders carry payment_window_minutes: 0 (a valid value, not an error); DeFi floating orders may carry a small nominal window — always key off fixed_rate_deadline. For fixed-rate orders, fixed_rate_deadline = created_on + payment_window_minutes. Countdown logic for UIs: Payment UI.
Example response (201):
{
"unique_reference": "ABCDEF123456",
"created_on": "2026-05-18T10:15:30Z",
"side": "BUY",
"status": "INITIAL",
"deposit_amount": "500.00000000",
"withdraw_amount": "0.01000000",
"deposit_currency": "USDTERC",
"withdraw_currency": "BTC",
"deposit_address": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
"deposit_address_extra_id": "",
"withdraw_address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"withdraw_address_extra_id": "",
"refund_address": "",
"refund_address_extra_id": "",
"payment_window_minutes": 60,
"fixed_rate_deadline": "2026-05-18T11:15:30Z",
"rate": "50000.00000000",
"deposit_transaction": "",
"withdraw_transaction": ""
}
DeFi orders
DeFi orders use the same endpoint with is_defi: true. The deposit_address returned is a proxy address; funds sent there are swapped non-custodially and delivered on the destination chain.
{
"deposit_currency": { "code": "SOL", "network": "SOL" },
"withdraw_currency": "BTC",
"withdraw_address": "bc1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"refund_address": "So1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"is_defi": true
}
side,deposit_amount,withdraw_amountare optional (treated asBUY; amount follows the actual deposit).- Minimum deposit: $20 USD equivalent; also respect the pair's
min_deposit_amountfromGET /rate/?is_defi=true. refund_addressis required when the destination (withdraw) chain is non-EVM — Solana, Tron, or Bitcoin. The address itself must be valid for the deposit currency's chain.- For fixed-rate DeFi, fetch a fixed-rate quote first and create the order with the same deposit amount and its
rate_id.
{
"unique_reference": "AB12CD",
"created_on": "2026-02-20T10:00:00.000000Z",
"side": "BUY",
"is_defi": true,
"is_fixed_rate": false,
"status": "INITIAL",
"deposit_currency": "SOL",
"withdraw_currency": "BTC",
"deposit_amount": null,
"withdraw_amount": null,
"deposit_address": "0xProxyAddressFromDeFi",
"withdraw_address": "bc1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"refund_address": "So1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"deposit_transaction": "",
"withdraw_transaction": "",
"refund_transaction": "",
"rate": "0.0",
"payment_window_minutes": 1,
"fixed_rate_deadline": null
}
is_defiGET /orders/{unique_reference}/ for a DeFi order may not include the is_defi flag itself. If your UI branches on order mode, persist the mode at creation time instead of inferring it from the detail response. See Payment UI patterns.
DeFi limit orders
Limit orders target a minimum receive amount instead of a quoted rate. is_limit_order must not be combined with is_fixed_rate, and rate_id does not apply.
{
"deposit_currency": { "code": "ETH", "network": "ETH" },
"withdraw_currency": { "code": "USDT", "network": "ETH" },
"withdraw_address": "0xYourUsersWallet",
"refund_address": "0xYourUsersWallet",
"is_defi": true,
"is_limit_order": true,
"limit_receive_amount": "20",
"limit_ttl": 3600
}
limit_receive_amount— human-readable destination units; converted internally to smallest units (read back aslimit_min_received, a string integer in smallest units).limit_ttl— seconds. Omitted or non-positive → the default TTL (currently 24 hours). Positive values must be at least 600 seconds; smaller values return400(limit_ttl must be at least 600 seconds (10 minutes).).- Read back
limit_expires_atfor the effective expiry.
DeFi direct execution (user-signed transaction)
For whitelisted referral codes, eligible DeFi orders also return an unsigned_transaction object next to deposit_address. Instead of transferring funds to the proxy address and waiting for the relayer, the user's wallet signs one transaction that pulls the deposit from the wallet, swaps it and delivers the output to withdraw_address. If anything fails, the whole transaction reverts and nothing leaves the wallet except gas.
Eligibility: fixed-rate orders on same-chain EVM routes. Floating-rate orders and limit orders are not offered direct execution. Ask your account manager to enable it for your referral code; without it the response is unchanged.
For a step-by-step test with calldata checks and a runnable script, see Direct Execution: Signing the Swap Yourself.
unsigned_transaction can also be missing on an eligible order, for example when no quote is available at that moment. The order is still created: use deposit_address, or request the transaction again with the refresh action below.
{
"chain_id": 8453,
"to": "0xDirectSwapEntryAddress",
"data": "0x...",
"value": "0",
"gas_estimate": null,
"gas_estimate_error": null,
"expires_at": "2026-09-09T15:12:03Z",
"quote_expires_at": "2026-09-09T15:12:33Z",
"approval": {
"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"spender": "0xDirectSwapEntryAddress",
"amount": "100000000"
},
"summary": {
"rate_mode": "floating",
"proxy_address": "0xProxyAddress",
"handler_address": "0xHandlerAddress",
"entry_address": "0xDirectSwapEntryAddress",
"token_in": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"token_out": "0x4200000000000000000000000000000000000006",
"amount_in": "100000000",
"swap_input": "98000000",
"protocol_fee": "2000000",
"gas_fee": "0",
"min_amount_out": "39811234567890123456",
"destination_address": "0xYourUsersWallet"
}
}
| Field | Meaning |
|---|---|
chain_id, to, data, value | The transaction to sign. value is the deposit amount for native input, otherwise "0". |
expires_at | Refresh after this time. The quote inside the calldata expires at quote_expires_at; expires_at leaves a safety margin for signing and inclusion. |
approval | ERC-20 allowance still needed before the transaction can be sent. null for native input, or once the sender's allowance already covers summary.amount_in. |
gas_estimate | Set when sender_address was supplied and the allowance is in place; gas_estimate_error carries the simulation error otherwise. |
summary | Amounts are integer strings in the token's smallest units (unlike the decimal deposit_amount). Before prompting the wallet, check that summary.destination_address equals your withdraw_address and that summary.amount_in is what the wallet will send. |
summary.min_amount_out | The least the transaction delivers, enforced on-chain: the transaction reverts below this amount. Compare it with the amount you show the user before prompting the wallet. |
summary.rate_mode | How this transaction was priced. fixed: min_amount_out is the amount promised when the order was created. floating: the calldata carries a fresh quote and min_amount_out is that quote's floor. It is not the order's rate type; read fixed_rate_deadline on the order for that. |
Flow:
- Create the order and keep both
deposit_addressandunsigned_transaction. - If
approvalis present, have the wallet approvespenderfor at leastamountoftoken. - Right before prompting, refresh with
POST /orders/{unique_reference}/unsigned_transaction/. Body fields are optional:sender_address(enables the allowance check and the gas estimate) anddeposit_amount(a decimal string in units of the deposit currency, for example"1.25"for 1.25 USDC, not smallest units; digits beyond the currency's precision are rounded down; defaults to the amount the order was created with). Always refresh onceexpires_athas passed. - Send the transaction from the wallet. Optionally report it with
POST /orders/{unique_reference}/unsigned_transaction/submitted/and body{"tx_hash": "0x..."}so the order completes without waiting for the chain sweep (202means not mined yet, retry). Poll the order as usual.
The proxy deposit_address keeps working: a user who sends funds there is served by the relayer as before.
Errors on the two actions return {"detail": "...", "code": "..."}. "Refresh" is POST /orders/{unique_reference}/unsigned_transaction/ and "submit" is POST /orders/{unique_reference}/unsigned_transaction/submitted/.
| Code | Status | Action | Meaning |
|---|---|---|---|
direct_execution_not_enabled | 409 | refresh | Direct execution is not enabled for this referral code or on this chain. |
direct_execution_unsupported_route | 409 | refresh, submit | The order is not eligible (see Eligibility above). |
direct_execution_unavailable | 409 | refresh, submit | Declined for another reason. Use deposit_address. |
order_not_pending | 409 | refresh, submit | The order is no longer pending (cancelled, funded or completed). |
fixed_rate_expired | 409 | refresh | The fixed-rate window has passed. Create a new order. |
promise_not_fillable | 409 | refresh | The promised amount cannot be filled at the current market. Create a new order. |
slippage_exceeded | 409 | refresh | The rate moved beyond the order's slippage tolerance since creation. |
proxy_already_funded | 409 | refresh | Funds already reached deposit_address; the relayer executes that deposit. |
tx_failed | 409 | submit | The transaction reverted on-chain. Nothing was swapped. |
amount_required | 400 | refresh | No amount is known for the order. Send deposit_amount. |
amount_invalid | 400 | refresh | deposit_amount is not a positive decimal. |
tx_hash_invalid | 400 | submit | tx_hash is not a transaction hash. |
tx_mismatch | 400 | submit | The transaction is not this order's direct execution. |
refresh_rate_limited | 429 | refresh | Too many refreshes. Retry after a short pause. |
quote_unavailable | 503 | refresh | No quote is available right now. Retry shortly. |
execution_service_unavailable | 503 | refresh, submit | The service is temporarily unavailable. Retry shortly. |
The transaction can be refreshed until the order's fixed_rate_deadline. After it the refresh action returns fixed_rate_expired, and once the unfunded order is cancelled it returns order_not_pending; create a new order in both cases. A direct transaction that was signed before the cancellation and lands afterwards is still recognised and completes the order.
List Orders
GET /orders/
Returns CeFi orders only with cursor pagination. DeFi orders do not appear in the list — track them individually via Get Order.
Request
curl -X GET "https://api.n.exchange/en/api/v2/orders/?page_size=25" \
-H "Accept: application/json" \
-H "Authorization: ApiKey YOUR_API_KEY"
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
cursor | string | No | Opaque cursor from pagination.next / pagination.previous |
page_size | integer | No | 1–100, default 25 |
pair | string | No | Pair name filter, e.g. BTCETH |
status | integer or string | No | Status filter — accepts the numeric code (?status=11) or the exact status name (?status=INITIAL). Mapping: Order Lifecycle. Unknown values return an empty list |
The list uses cursor pagination — there is no page parameter and no total count. Follow pagination.next until has_next is false. Ordering is -created_on, -id (newest first).
Response
{
"pagination": {
"next": "https://api.n.exchange/en/api/v2/orders/?cursor=cD0yMDI2LTA1LTE3VDE0JTNBMDAlM0EwMFo%3D",
"previous": null,
"has_next": true,
"has_previous": false,
"page_size": 25,
"ordering": ["-created_on", "-id"]
},
"results": [
{
"unique_reference": "ABCDEF123456",
"created_on": "2026-05-18T10:15:30Z",
"side": "BUY",
"withdraw_amount": "0.01000000",
"deposit_amount": "500.00000000",
"deposit_currency": "USDTERC",
"withdraw_currency": "BTC",
"withdraw_address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
"withdraw_address_extra_id": "",
"deposit_address": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
"deposit_address_extra_id": "",
"refund_address": "",
"refund_address_extra_id": "",
"payment_window_minutes": 60,
"fixed_rate_deadline": "2026-05-18T11:15:30Z",
"status": "COMPLETED",
"rate": "50000.00000000",
"deposit_transaction": "8a3f4c2d9e1b6a5f8c2d9e1b6a5f8c2d9e1b6a5f8c2d9e1b6a5f8c2d9e1b6a5f",
"withdraw_transaction": "0xfeed1234feed1234feed1234feed1234feed1234feed1234feed1234feed1234"
}
]
}
Get Order
GET /orders/{unique_reference}/
Works for both CeFi and DeFi orders. Returns every OrderV2 field plus:
| Field | Type | Description |
|---|---|---|
amount_usd | number/null | USD equivalent value. CeFi orders only; null/absent for DeFi |
curl -X GET "https://api.n.exchange/en/api/v2/orders/ABCDEF123456/" \
-H "Accept: application/json" \
-H "Authorization: ApiKey YOUR_API_KEY"
Poll this endpoint to track progress — recommended cadence and stop conditions in Order Tracking.
Update Order
PATCH /orders/{unique_reference}/
All fields optional; send only what you are changing.
| Field | Type | Description |
|---|---|---|
withdraw_address | string | Set the withdrawal address — only once, and only before processing starts |
withdraw_address_extra_id | string | Memo/tag for the withdrawal (max 127 chars) |
marked_as_paid | boolean | true = mark the order paid (optionally with deposit_transaction for faster crediting); false = cancel the pending payment |
deposit_transaction | string | Self-served deposit transaction hash |
referral_code | string | Referral code (≤20 chars; must belong to the API key owner) |
curl -X PATCH "https://api.n.exchange/en/api/v2/orders/ABCDEF123456/" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: ApiKey YOUR_API_KEY" \
-d '{
"marked_as_paid": true,
"deposit_transaction": "8a3f4c2d9e1b6a5f8c2d9e1b6a5f8c2d9e1b6a5f8c2d9e1b6a5f8c2d9e1b6a5f"
}'
Returns 200 with the updated OrderV2.
The PATCH parameter is marked_as_paid. The related read-only flag exposed on some order payloads is user_marked_as_paid — the value you set is reflected there. Marking as paid is optional: deposits are detected automatically by chain monitoring; use it (with the tx hash) to speed crediting up, or marked_as_paid: false to cancel an unpaid order.
Re-submitting a deposit_transaction that is already recorded returns a 400 with an "already exists" style validation message — safe to treat as success in retry logic. See Errors & Rate Limits.
Refund a DeFi Order
POST /orders/{unique_reference}/refund/
Asks for the deposit of a funded DeFi order to be sent back, as long as the swap has not been executed. The usual case is a deposit below the order minimum: such an order waits for the user to send the missing amount to the same deposit address, and this call lets you return the funds instead.
- Who can call it: the partner whose referral code is on the order (API key owner or token user), or the order's own user. For any other caller the order does not exist (
404). Widget-scoped API keys cannot refund (403). - Where the funds go: always to the order's
refund_address, or to itswithdraw_addresswhen the order was created without one. Both are fixed at creation; the request has no body and cannot redirect the refund. - Asynchronous:
202means the refund was requested. The response is the order as it stands (stillINITIAL). Keep polling Get Order: the order moves toINITIATED REFUNDwhen the refund transaction is sent, then toREFUNDEDwithrefund_transactionset. - Daily limit: refunds, whether you request them or they happen automatically, share a daily limit per partner. Past it, and for deposits too small to be worth their network fee, the answer is
409and our support team handles the order.
curl -X POST "https://api.n.exchange/en/api/v2/orders/DEFI234567/refund/" \
-H "Accept: application/json" \
-H "Authorization: ApiKey YOUR_API_KEY"
| Status | Meaning |
|---|---|
202 | Refund requested; body is the order (OrderV2) |
403 | Widget-scoped API keys cannot refund; use your standard API key |
404 | Unknown reference, not a DeFi order, or not yours |
409 | Nothing to refund (unfunded, being executed, already executed or refunded), refund requests currently disabled, or daily refund limit reached. detail says which |
503 | Refunds temporarily unavailable; retry later |
You do not need to call this endpoint for every short deposit. A DeFi deposit that stays below the order minimum for about 10 minutes is refunded automatically, within the same daily limit. The call is for when you want the refund right away.
Errors
| Status | When | Body shape |
|---|---|---|
400 | Validation failure — missing fields, invalid/expired rate_id, amount outside limits, markup+referral_rate together, invalid address | {"detail": "...", "errors": {"field": ["..."]}} |
401 | Missing/invalid API key, or referral code not owned by the key owner | {"detail": "..."} |
404 | Unknown unique_reference | {"detail": "Not found"} |
409 | Refund not possible for this order right now | {"detail": "..."} |
503 | Refund temporarily unavailable | {"detail": "..."} |
Retryability of specific validation errors (expired quotes, transient DeFi quote failures) is catalogued in Errors & Rate Limits.
Next steps
- Order Lifecycle — statuses, flows, refunds
- Order Tracking — polling guidance
- Webhooks — push notifications