Prerequisites
- Payward Services API credentials (see the Authentication guide).
- A verified user with at least one account and sufficient balance in the source asset.
- Examples target
https://api.services.payward.com and read credentials from the PWS_API_KEY and PWS_API_SECRET environment variables.
Workflow
Create a price trigger swap
Submit the swap with its trigger condition.POST /v1/accounts/{account_id}/price-trigger-swaps
Monitor or cancel
Track status, list active swaps, or cancel one that hasn’t fired yet.
GET /v1/accounts/{account_id}/price-trigger-swaps
GET /v1/accounts/{account_id}/price-trigger-swaps/{price_trigger_swap_id}
POST /v1/accounts/{account_id}/price-trigger-swaps/{price_trigger_swap_id}/cancel
Trigger conditions
The when block specifies the price to monitor and the threshold that fires the trade. Set the base and quote AssetRef pair, then exactly one of drops_to or rises_to:
| Field | Description |
|---|
drops_to | Fire when the price falls to or below this value (e.g. “buy on dip”) |
rises_to | Fire when the price rises to or above this value (e.g. “take profit”) |
The threshold is a base/quote rate as a decimal string. For example, with base.symbol = BTC and quote.symbol = USD, drops_to: "50000.00" means “fire when 1 BTC trades at 50,000 USD or below”.
Examples:
- Buy the Dip: “Buy BTC when BTC/USD drops to 50,000” — set
base.symbol to BTC, quote.symbol to USD, and drops_to to "50000.00". Pair with a trade whose from is USD and to is BTC.
- Stop the Loss: “Sell BTC when BTC/USD drops to 50,000” — same
when block, but with trade.from set to BTC and trade.to set to USD.
- Join the Rally: “Buy BTC when BTC/USD rises to 80,000” — same pair, replace
drops_to with rises_to: "80000.00". Trade goes USD → BTC.
- Take the Profit: “Sell BTC when BTC/USD rises to 80,000” — same
when block as Join the Rally, but with trade.from set to BTC and trade.to set to USD.
Execution behavior
Price trigger swaps are not guaranteed to execute at exactly the threshold price. The threshold is a trigger, not a limit price:
- Buy the Dip / Stop the Loss: The swap triggers when the market price drops to or below
drops_to. The actual execution price may be lower than the threshold.
- Join the Rally / Take the Profit: The swap triggers when the market price rises to or above
rises_to. The actual execution price may be higher than the threshold.
For swaps 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 creation. Because the execution price may be higher than the threshold, the
final spend amount is unbounded upward — this prevents a situation where a user creates a swap that could spend more
fiat than the client has approved.
Authentication setup
Authenticated endpoints require an HMAC-SHA512 request signature in the API-Sign header and a monotonically increasing nonce in the API-Nonce header. The helper below derives the signature from the URL path, request body, and nonce. See the Authentication guide for the full algorithm.
import os
import json
import time
import uuid
import hashlib
import hmac
import base64
import urllib.parse
import requests
API_KEY = os.environ["PWS_API_KEY"]
API_SECRET = os.environ["PWS_API_SECRET"]
BASE_URL = "https://api.services.payward.com"
def get_payward_signature(urlpath, data, secret, nonce, params=None):
encoded = (
str(nonce).encode("utf-8")
if data is None
else (str(nonce) + json.dumps(data)).encode("utf-8")
)
sign_path = urlpath
if params:
sign_path += "?" + urllib.parse.urlencode(params)
message = sign_path.encode() + hashlib.sha256(encoded).digest()
mac = hmac.new(base64.b64decode(secret), message, hashlib.sha512)
return base64.b64encode(mac.digest()).decode()
def pws_headers(signature, nonce, idempotent=False):
headers = {
"API-Key": API_KEY,
"API-Sign": signature,
"API-Nonce": str(nonce),
"Content-Type": "application/json",
}
if idempotent:
headers["Idempotency-Key"] = str(uuid.uuid4())
return headers
Every PWS write endpoint (POST / PUT / DELETE) accepts an Idempotency-Key header containing a UUIDv4. Generate
a fresh key per logical attempt — replays of the same key return the original response body and the
Idempotent-Replayed: true response header, which keeps retries safe under timeouts and connection drops.
Step 1: create a price trigger swap
Submit the swap with trade, when, and an optional external_reference. The external_reference is a free-form string for client-side correlation — it’s echoed back on reads and webhook events but is not interpreted by Payward. The response returns only the new swap’s id; fetch GET /v1/accounts/{account_id}/price-trigger-swaps/{price_trigger_swap_id} to read the full resource.
Set the amount on exactly one of trade.from or trade.to to indicate which side of the trade is fixed. Setting both or neither returns 400 Bad Request.
def create_price_trigger_swap(account_id):
endpoint = f"/v1/accounts/{account_id}/price-trigger-swaps"
nonce = time.time_ns()
body = {
"external_reference": "buy-dip-btc-001",
"trade": {
"from": {"symbol": "USD", "type": "fiat", "amount": "100.00"},
"to": {"symbol": "BTC", "type": "crypto"},
"fee": {"bps": 50},
},
"when": {
"base": {"symbol": "BTC", "type": "crypto"},
"quote": {"symbol": "USD", "type": "fiat"},
"drops_to": "50000.00",
},
}
signature = get_payward_signature(endpoint, body, API_SECRET, nonce)
response = requests.post(
BASE_URL + endpoint,
headers=pws_headers(signature, nonce, idempotent=True),
json=body,
)
return response.json()
account_id = "NL00KRAK0123456789"
created = create_price_trigger_swap(account_id)
swap_id = created["data"]["id"]
print(f"Swap created: {swap_id}")
Response example
{
"data": {
"id": "swap_01J0M7C0Z9F8YX3GQH8E"
}
}
The response also carries a Location header pointing at the new resource (e.g. /v1/accounts/NL00KRAK0123456789/price-trigger-swaps/swap_01J0M7C0Z9F8YX3GQH8E).
Step 2: monitor swaps
Get a single swap
def get_price_trigger_swap(account_id, swap_id):
endpoint = f"/v1/accounts/{account_id}/price-trigger-swaps/{swap_id}"
nonce = time.time_ns()
signature = get_payward_signature(endpoint, None, API_SECRET, nonce)
response = requests.get(BASE_URL + endpoint, headers=pws_headers(signature, nonce))
return response.json()
swap = get_price_trigger_swap(account_id, swap_id)
print(f"Status: {swap['data']['status']}")
Response example
{
"data": {
"id": "swap_01J0M7C0Z9F8YX3GQH8E",
"external_reference": "buy-dip-btc-001",
"trade": {
"from": { "symbol": "USD", "type": "fiat", "amount": "100.00" },
"to": { "symbol": "BTC", "type": "crypto" }
},
"when": {
"base": { "symbol": "BTC", "type": "crypto" },
"quote": { "symbol": "USD", "type": "fiat" },
"drops_to": "50000.00"
},
"status": "active"
}
}
When status is completed, the response also includes trade.fees and trade.rate reflecting what was realised at execution time.
List swaps
Filter by status using the statuses query parameter (repeat for multiple values, or omit to include any status). Use page_token and page_size (default 20, max 100) for pagination — next_page_token is omitted on the final page.
def list_price_trigger_swaps(account_id, statuses=None, page_token=None, page_size=20):
endpoint = f"/v1/accounts/{account_id}/price-trigger-swaps"
nonce = time.time_ns()
params = []
if statuses:
params.extend(("statuses", s) for s in statuses)
if page_token:
params.append(("page_token", page_token))
params.append(("page_size", page_size))
signature = get_payward_signature(endpoint, None, API_SECRET, nonce, params)
response = requests.get(
BASE_URL + endpoint,
headers=pws_headers(signature, nonce),
params=params,
)
return response.json()
page = list_price_trigger_swaps(account_id, statuses=["active"])
for s in page["data"]:
print(f"{s.get('external_reference', s['id'])} — {s['status']}")
Swap statuses
| Status | Description |
|---|
active | Swap is monitoring the market, waiting for the trigger condition to be met |
completed | Trigger condition was met and the trade executed |
cancelled | Cancelled at the user’s request before the trigger fired |
failed | Terminated by Payward before completing, for a non-user reason |
A cancelled swap is always user-initiated and carries no extra reason. When status is failed, the response includes a failure_reason field explaining why it was terminated. The value is one of:
failure_reason | Description |
|---|
user_locked | The user’s account was locked |
expired_payment_method | The funding payment method expired |
asset_unavailable | One of the assets in the pair became unavailable for trading |
pair_unavailable | The trading pair became unavailable |
retries_exhausted | The swap exhausted its execution retries |
funding_method_deleted | The funding method backing the swap was deleted |
other | Terminated for a reason not listed above |
Step 3: cancel a swap
Cancel an active swap that hasn’t triggered yet. The body is empty.
Swaps in completed, cancelled, or failed status are terminal and return 409 Conflict with code: price_trigger_swap_not_cancellable.
def cancel_price_trigger_swap(account_id, swap_id):
endpoint = f"/v1/accounts/{account_id}/price-trigger-swaps/{swap_id}/cancel"
nonce = time.time_ns()
body = {}
signature = get_payward_signature(endpoint, body, API_SECRET, nonce)
response = requests.post(
BASE_URL + endpoint,
headers=pws_headers(signature, nonce, idempotent=True),
json=body,
)
response.raise_for_status()
cancel_price_trigger_swap(account_id, swap_id)
swap = get_price_trigger_swap(account_id, swap_id)
print(f"Status: {swap['data']['status']}") # cancelled
Webhook events
| Event type | Delivered when |
|---|
price_trigger_swap.executed | Trigger fired and the resulting trade settled successfully |
price_trigger_swap.execution_failed | Trigger fired but the trade failed to execute (may retry, may move to failed) |
price_trigger_swap.cancelled | Swap moved to cancelled status |
Portfolio transactions
Executed price trigger swaps appear in the List Portfolio Transactions endpoint. Filter by transaction type:
| Transaction type | Description |
|---|
price_trigger_swap | Successfully executed price trigger swap |
price_trigger_swap_failed | Price trigger swap execution that failed |
GET /v1/accounts/{account_id}/portfolio/transactions?types=price_trigger_swap,price_trigger_swap_failed
API reference
| Endpoint | Method | Description |
|---|
/v1/accounts/{account_id}/price-trigger-swaps | POST | Create a price trigger swap |
/v1/accounts/{account_id}/price-trigger-swaps | GET | List price trigger swaps (filterable by status) |
/v1/accounts/{account_id}/price-trigger-swaps/{price_trigger_swap_id} | GET | Get a single price trigger swap |
/v1/accounts/{account_id}/price-trigger-swaps/{price_trigger_swap_id}/cancel | POST | Cancel an active price trigger swap |