Skip to main content

Documentation Index

Fetch the complete documentation index at: https://kraken-sandbox.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

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