Skip to main content
This guide walks you through executing your first trade using the Payward Embed API.

Prerequisites

Before you begin, make sure you have:
  • Payward Embed API credentials (see Authentication Guide)
  • A verified user with an IIBAN (Internet International Bank Account Number)
  • Sufficient balance in the user’s account

Trading workflow

The Embed API uses a quote-based trading model:
┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐
│  Request Quote  │ ───▶ │  Execute Quote  │ ───▶ │  Monitor Status │
└─────────────────┘      └─────────────────┘      └─────────────────┘
     POST /quotes           PUT /quotes/{id}        GET /quotes/{id}
  1. Request Quote: Get a price quote for your desired trade
  2. Execute Quote: Confirm and execute the quoted trade
  3. Monitor Status: Poll until the trade completes or listen for the quote.executed webhook

Step 1: Request a quote

Request a quote to buy cryptocurrency with fiat currency. The quote locks in a price for a short period.

Quote types

TypeDescriptionUse Case
spendSpecify amount to spend”I want to spend €50 on BTC”
receiveSpecify amount to receive”I want to receive 0.001 BTC”

Examples

import os
import json
import time
import hashlib
import hmac
import base64
import urllib.parse
import requests

API_KEY = os.environ.get("PAYWARD_API_KEY")
API_SECRET = os.environ.get("PAYWARD_API_SECRET")
BASE_URL = "https://nexus.kraken.com"


def get_payward_signature(urlpath, data, secret, nonce, params=None):
    if data is None:
        encoded = str(nonce).encode("utf-8")
    else:
        encoded = (str(nonce) + json.dumps(data)).encode("utf-8")
    
    sign_path = urlpath
    if params:
        query = urllib.parse.urlencode(params)
        sign_path += f"?{query}"

    message = sign_path.encode() + hashlib.sha256(encoded).digest()
    mac = hmac.new(base64.b64decode(secret), message, hashlib.sha512)
    return base64.b64encode(mac.digest()).decode()


def request_quote(user_id):
    endpoint = "/b2b/quotes"
    nonce = int(time.time() * 1000000000)  # Nanoseconds
    
    body = {
        "type": "spend",           # 'spend' fiat to buy crypto
        "amount": {
            "asset": "EUR",        # Currency to spend
            "amount": "50.00",     # Amount to spend
        },
        "fee_bps": "100",          # Fee in basis points (1%)
        "spread_bps": "100",       # Spread in basis points (1%)
        "quote": {
            "asset": "BTC",        # Cryptocurrency to receive
        },
    }
    
    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()


user_id = "YOUR_USER_IIBAN"
quote = request_quote(user_id)
print(f"Quote ID: {quote['result']['quote_id']}")
print(f"You spend: {quote['result']['spend']['total']} {quote['result']['spend']['asset']}")
print(f"You receive: {quote['result']['receive']['total']} {quote['result']['receive']['asset']}")

Response example

{
  "result": {
    "quote_id": "BQEXAMP-LE123-ABCDEF",
    "type": "spend",
    "status": "new",
    "expires": "2026-01-26T12:00:30Z",
    "spend": {
      "asset": "EUR",
      "total": "50.00",
      "fee": "0.50",
      "subtotal": "49.50"
    },
    "receive": {
      "asset": "BTC",
      "total": "0.00052341",
      "fee": "0.00000000",
      "subtotal": "0.00052341"
    },
    "unit_price": {
      "asset": "BTC",
      "unit_price": "94567.50",
      "denomination_asset": "EUR"
    }
  }
}
Quotes expire after 120 seconds (2 minutes). Execute the quote promptly or request a new one if it expires. The exact expiration time is returned in the expires field of the quote response.

Step 2: Execute the quote

Once you have a quote, execute it by making a PUT request with the quote ID.
def execute_quote(user_id, quote_id):
    endpoint = f"/b2b/quotes/{quote_id}"
    nonce = int(time.time() * 1000000000)  # Nanoseconds
    
    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.put(
        f"{BASE_URL}{endpoint}",
        headers=headers,
        params=params,
    )
    return response.json()


result = execute_quote(user_id, quote["result"]["quote_id"])
print(f"Status: {result['result']['status']}")

Step 3: Monitor trade status

After execution, poll the quote status until the trade completes.

Status values

StatusDescription
newQuote created, awaiting execution
acceptedQuote has been accepted for execution
order_completeTrade order has been completed
credit_transfer_completeTrade fully completed, funds transferred
quote_cancelledQuote was cancelled or expired
quote_execution_failedTrade execution failed

Examples

import time

def get_quote_status(user_id, quote_id):
    endpoint = f"/b2b/quotes/{quote_id}"
    nonce = int(time.time() * 1000000000)  # Nanoseconds
    
    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()


def wait_for_completion(user_id, quote_id, max_attempts=60):
    terminal_statuses = ["credit_transfer_complete", "quote_cancelled", "quote_execution_failed"]
    
    for i in range(max_attempts):
        status = get_quote_status(user_id, quote_id)
        current_status = status["result"]["status"]
        print(f"Status: {current_status}")
        
        if current_status == "credit_transfer_complete":
            print("Trade completed!")
            return status
        
        if current_status in terminal_statuses:
            raise Exception(f"Trade ended with status: {current_status}")
        
        time.sleep(1)
    
    raise Exception("Trade did not complete in time")


final = wait_for_completion(user_id, quote["result"]["quote_id"])

Error handling

Common errors

ErrorCauseSolution
quote_expiredQuote timeout exceededRequest a new quote and execute faster
insufficient_balanceUser lacks fundsCheck user balance before trading
invalid_amountAmount too small/largeCheck min/max limits for the asset
asset_disabledAsset not tradableUse List Assets to check availability

Best practices

  1. Handle quote expiration: Always be prepared to request a new quote
  2. Check asset availability: Call List Assets before trading
  3. Verify user balance: Ensure sufficient funds before requesting quotes
  4. Implement retry logic: Network issues can cause temporary failures
  5. Log trace IDs: Store the x-trace-id header for debugging