Skip to main content

Embed: Custom Orders

Custom orders are price-triggered trades that execute automatically when an asset reaches a target price. Use them to implement strategies like "Buy the Dip" (buy when price drops to a target), "Join the Rally" (buy when price rises to ride momentum), "Take the Profit" (sell when price rises to a target), and "Stop the Loss" (sell when price drops to limit losses) for your users.

Prerequisites

  • Payward Embed API credentials (see Authentication Guide)
  • A verified user with an IIBAN and sufficient balance

Custom Order Workflow

┌──────────────────────┐     ┌──────────────────┐     ┌────────────────────┐
│ Prospective Quote │ ──▶ │ Create Order │ ──▶ │ Monitor / Cancel │
│ (optional preview) │ │ │ │ │
└──────────────────────┘ └──────────────────┘ └────────────────────┘
POST /quotes/prospective POST /custom-orders GET /custom-orders
GET /custom-orders/{id}
POST /custom-orders/{id}/cancel
  1. Prospective Quote (optional): Preview indicative pricing at the target price so you can show your user what the trade would look like before they commit.
  2. Create Order: Submit the custom order with a price trigger. The system monitors the market and executes the trade automatically when the condition is met.
  3. Monitor / Cancel: List or retrieve orders to check their status. Cancel orders that haven't triggered yet.

Trigger Conditions

ConditionDescription
lteTrigger when price is less than or equal to target
gteTrigger when price is greater than or equal to target

The trigger price is denominated as a base_asset/quote_asset rate. For example, if base_asset is BTC and quote_asset is USD, a target_price of "100000.00" means "1 BTC = 100,000 USD". Equivalently, if base_asset is USD and quote_asset is BTC, a target_price of "0.00001" means "1 USD = 0.00001 BTC".

Examples:

  • Buy the Dip: "Buy BTC when BTC/USD drops to 100,000" -- set base_asset to BTC, quote_asset to USD, target_price to "100000.00", and condition to lte. The order triggers when the BTC/USD price is at or below 100,000.
  • Stop the Loss: "Sell BTC when BTC/USD drops to 100,000" -- same trigger configuration as above, but with a sell action. The order triggers when the BTC/USD price is at or below 100,000.
  • Join the Rally: "Buy BTC when BTC/USD rises to 100,000" -- set base_asset to BTC, quote_asset to USD, target_price to "100000.00", and condition to gte. The order triggers when the BTC/USD price is at or above 100,000.
  • Take the Profit: "Sell BTC when BTC/USD rises to 100,000" -- same trigger configuration as above, but with a sell action. The order triggers when the BTC/USD price is at or above 100,000.

Execution Behavior

Custom orders are not guaranteed to execute at exactly the target price. The target price is a trigger threshold, not a limit price:

  • Buy the Dip / Stop the Loss: The order triggers when the market price drops to or below the target. The actual execution price may be lower than the target price.
  • Join the Rally / Take the Profit: The order triggers when the market price rises to or above the target. The actual execution price may be higher than the target price.
note

For orders where the execution price is higher than the market price and the user is spending the client's reserve fiat currency, an error will be returned at order creation. Because the execution price may be higher than the target, the final spend amount is unbounded upward -- this prevents a situation where a user creates an order that could spend more fiat than the client has approved.

Step 1: Request a Prospective Quote (Optional)

Before creating an order, you can request a prospective quote to display indicative fees and amounts to the user at the target price. This is useful for showing the user what the trade would cost before they commit to creating the order. If the trigger is omitted, the response reflects current market prices instead.

caution

Prospective quote fees are indicative and may not be exact. The final execution price is determined when the trigger condition is met, which may differ from the target price. As a result, the actual fees at execution time may vary from those shown in the prospective quote.

def request_prospective_quote(user_id):
endpoint = "/b2b/quotes/prospective"
nonce = int(time.time() * 1000000000)

body = {
"action": {
"type": "spend",
"amount": {
"asset": "USD",
"amount": "500.00",
},
"quote": {
"asset": "BTC",
},
"fee_bps": "100",
"spread_bps": "100",
},
"trigger": {
"type": "price",
"base_asset": "BTC",
"quote_asset": "USD",
"target_price": "100000.00",
"condition": "lte",
},
}

params = {"user": user_id}
signature = get_payward_signature(endpoint, body, API_SECRET, nonce, params)

headers = {
"API-Key": API_KEY,
"API-Sign": signature,
"API-Nonce": str(nonce),
"Content-Type": "application/json",
}

response = requests.post(
f"{BASE_URL}{endpoint}",
headers=headers,
params=params,
json=body,
)
return response.json()

Response Example

{
"result": {
"type": "spend",
"spend": {
"asset": "USD",
"asset_class": "currency",
"total": "500.00",
"fee": "5.00",
"subtotal": "495.00"
},
"receive": {
"asset": "BTC",
"asset_class": "currency",
"total": "0.00990000",
"fee": "0.00000000",
"subtotal": "0.00990000"
},
"unit_price": {
"asset": "BTC",
"unit_price": "100000.00",
"denomination_asset": "USD"
}
}
}

If you pass quote_currency in the request body, the response will also include quoted_spend, quoted_receive, and quoted_unit_price fields denominated in that currency.

Step 2: Create a Custom Order

Submit the order with a name, trigger, and action. The name is a display label your user can identify the order by.

def create_custom_order(user_id):
endpoint = "/b2b/custom-orders"
nonce = int(time.time() * 1000000000)

body = {
"name": "Buy BTC at 100k",
"trigger": {
"type": "price",
"base_asset": "BTC",
"quote_asset": "USD",
"target_price": "100000.00",
"condition": "lte",
},
"action": {
"type": "spend",
"amount": {
"asset": "USD",
"amount": "500.00",
},
"quote": {
"asset": "BTC",
},
"fee_bps": "100",
"spread_bps": "100",
},
}

params = {"user": user_id}
signature = get_payward_signature(endpoint, body, API_SECRET, nonce, params)

headers = {
"API-Key": API_KEY,
"API-Sign": signature,
"API-Nonce": str(nonce),
"Content-Type": "application/json",
}

response = requests.post(
f"{BASE_URL}{endpoint}",
headers=headers,
params=params,
json=body,
)
return response.json()


order = create_custom_order(user_id)
order_id = order["result"]["order"]["id"]
print(f"Order created: {order_id}")
print(f"Status: {order['result']['order']['status']['status']}")

Response Example

{
"result": {
"order": {
"id": "sched_abc123",
"name": "Buy BTC at 100k",
"action": {
"type": "spend",
"amount": {
"asset": "USD",
"asset_class": "currency",
"amount": "500.00"
},
"quote": {
"asset": "BTC"
},
"fee_bps": "100",
"spread_bps": "100"
},
"trigger": {
"type": "price",
"base_asset": "BTC",
"quote_asset": "USD",
"target_price": "100000.00",
"condition": "lte"
},
"created_at": "2026-02-20T10:00:00Z",
"updated_at": "2026-02-20T10:00:00Z",
"status": {
"status": "active"
}
}
}
}

Step 3: Monitor Orders

Get a single order

def get_custom_order(user_id, order_id):
endpoint = f"/b2b/custom-orders/{order_id}"
nonce = int(time.time() * 1000000000)

params = {"user": user_id}
signature = get_payward_signature(endpoint, None, API_SECRET, nonce, params)

headers = {
"API-Key": API_KEY,
"API-Sign": signature,
"API-Nonce": str(nonce),
}

response = requests.get(
f"{BASE_URL}{endpoint}",
headers=headers,
params=params,
)
return response.json()

List orders

You can filter by status using the statuses query parameter. Valid values are active, cancelled, and completed. If omitted, all statuses are returned. Use the cursor parameter for pagination.

def list_custom_orders(user_id, statuses=None):
endpoint = "/b2b/custom-orders"
nonce = int(time.time() * 1000000000)

params = {"user": user_id}
if statuses:
params["statuses"] = statuses # e.g. ["active", "completed"]

signature = get_payward_signature(endpoint, None, API_SECRET, nonce, params)

headers = {
"API-Key": API_KEY,
"API-Sign": signature,
"API-Nonce": str(nonce),
}

response = requests.get(
f"{BASE_URL}{endpoint}",
headers=headers,
params=params,
)
return response.json()


# List only active orders
orders = list_custom_orders(user_id, statuses=["active"])
for o in orders["result"]["orders"]:
print(f"{o['name']}{o['status']['status']}")

Order Statuses

StatusDescription
activeOrder is monitoring the market, waiting for the trigger condition to be met
completedTrigger condition was met and the trade executed
cancelledOrder was cancelled (by user request or system)

When an order is cancelled, the response includes a reason field explaining why.

Step 4: Cancel an Order

Cancel an active order that hasn't triggered yet.

def cancel_custom_order(user_id, order_id):
endpoint = f"/b2b/custom-orders/{order_id}/cancel"
nonce = int(time.time() * 1000000000)

params = {"user": user_id}
signature = get_payward_signature(endpoint, None, API_SECRET, nonce, params)

headers = {
"API-Key": API_KEY,
"API-Sign": signature,
"API-Nonce": str(nonce),
}

response = requests.post(
f"{BASE_URL}{endpoint}",
headers=headers,
params=params,
)
return response.json()


result = cancel_custom_order(user_id, order_id)
print(f"Status: {result['result']['order']['status']['status']}")
# Output: Status: cancelled

Portfolio Transactions

Executed custom orders appear in the List Portfolio Transactions endpoint with the following transaction types:

Transaction TypeDescription
custom_orderSuccessfully executed custom order
custom_order_failedCustom order execution that failed

Filter for custom order transactions specifically:

GET /b2b/portfolio/transactions?user={iiban}&types=custom_order,custom_order_failed

API Reference

EndpointMethodDescription
/b2b/quotes/prospectivePOSTGet indicative pricing at a target price
/b2b/custom-ordersPOSTCreate a custom order
/b2b/custom-ordersGETList custom orders (filterable by status)
/b2b/custom-orders/{order_id}GETGet a single custom order
/b2b/custom-orders/{id}/cancelPOSTCancel an active custom order

Further Reading

  • Custom Orders on the Kraken App -- Kraken's product FAQ for custom orders. While written for the consumer app, it provides a useful reference for how custom order functionality works in practice.