Python USDT Payment API Integration
Create USDT TRC-20 payment links with Python, retry requests with a stable idempotency key and verify payment signatures.
Keep the integration on your server
This example uses Python 3.11+ and built-in modules, without a client SDK. Importing the module makes no API call. Never ship an API key or signing key to a browser bundle.
First configure the HTTPS webhook. Set DEDYX_API_KEY and DEDYX_WALLET in your server environment. Download dedyx_client.py and import it from your backend.
The code is an integration starting point, not a complete order system. Add your durable order storage, request body limits, receiver routing and fulfilment transaction. The examples use synthetic orders; replace the sample fields before making a real request.
Create a payment with safe retries
"""Python 3.11+ server-side example; no calls or credentials at import time."""
import hashlib
import hmac
import json
import random
import re
import time
from urllib.error import HTTPError, URLError
from urllib.request import HTTPRedirectHandler, Request, build_opener
API = 'https://api.dedyx.com/api/v1'
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None # Never forward an API key to a redirected host.
def create_payment(api_key, wallet, amount, description, idempotency_key):
"""Persist the key and these fields with your order BEFORE calling this function."""
if not re.fullmatch(r'[A-Za-z0-9._:-]{8,128}', idempotency_key):
raise ValueError('Use a persisted 8–128 character idempotency key.')
body = json.dumps({'merchant_wallet': wallet, 'amount': amount,
'description': description}, separators=(',', ':')).encode()
request = Request(API + '/payments', data=body, method='POST', headers={
'X-API-Key': api_key, 'Idempotency-Key': idempotency_key,
'Content-Type': 'application/json'})
opener = build_opener(NoRedirect())
for attempt in range(3):
delay = 0.5 * (2 ** attempt) + random.uniform(0, 0.25)
try:
with opener.open(request, timeout=10) as response:
return json.load(response)
except HTTPError as error:
if error.code not in (429, 500, 502, 503, 504) or attempt == 2:
raise
retry_after = error.headers.get('Retry-After', '')
if retry_after.isascii() and retry_after.isdigit():
seconds = int(retry_after)
if seconds > 60:
raise # Schedule later with the SAME stored key/body.
delay = max(delay, seconds)
error.close() # nginx may return HTML; do not assume an error JSON body.
except (URLError, TimeoutError):
if attempt == 2:
raise # Outcome may be uncertain; preserve the key and reconcile.
time.sleep(delay)The client uses a 10-second timeout and at most three attempts. 429 and selected 5xx responses are retried with the same serialized body and idempotency key. Numeric Retry-After is respected; long waits are left for your job scheduler. nginx errors can be HTML, so the client checks the status without assuming JSON.
409 is not blindly retried: check missing webhook settings, an occupied address or a changed body for the same key. For exhausted or uncertain retries keep the stored key and reconcile; replay retention is 24 hours by default. Keys must contain 8–128 ASCII letters, digits or ._:-.
Connect the response to your order
import os
from dedyx_client import create_payment
# These fields must come from a persisted order record.
payment = create_payment(
api_key=os.environ["DEDYX_API_KEY"],
wallet=os.environ["DEDYX_WALLET"],
amount="49.00",
description="Order #1024",
idempotency_key="order-1024-v1",
)
# Persist payment["payment_id"] and the response before redirecting.
print(payment["payment_url"])Save the response together with your order before redirecting the customer to payment_url. Reuse the same key and request fields for retries of that logical order. Do not put personal information in description.
Verify the original webhook body
import hashlib
import hmac
import json
import re
import time
def verify_webhook(raw_body, timestamp, signature, header_event_id, key_id, keys, now=None):
"""keys maps saved X-Dedyx-Key-ID values to the full whsec_... string."""
if not re.fullmatch(r'[0-9]{1,12}', timestamp or ''):
raise ValueError('Invalid timestamp')
if abs((time.time() if now is None else now) - int(timestamp)) > 300:
raise ValueError('Stale signature')
if key_id not in keys or not re.fullmatch(r'v1=[0-9a-f]{64}', signature or ''):
raise ValueError('Unknown signing key or signature format')
expected = 'v1=' + hmac.new(keys[key_id].encode(), timestamp.encode() + b'.' + raw_body,
hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
raise ValueError('Invalid signature')
event = json.loads(raw_body)
if not isinstance(event, dict) or not isinstance(event.get('event_id'), str) or not event['event_id'] or event['event_id'] != header_event_id:
raise ValueError('Event ID mismatch')
# A valid signature is only the first check; validate your own order before fulfilment.
return eventPass the raw request bytes/Buffer, X-Dedyx-Timestamp, X-Dedyx-Signature, X-Dedyx-Event-ID and X-Dedyx-Key-ID. The keys mapping contains your saved key IDs and full whsec_... secrets. Do not run JSON middleware before capturing the raw bytes.
After verification, require PAID, match the event to your stored order and compare the exact amount. Persist event_id and fulfilment atomically. A duplicate returns 2xx without repeating the action. See delivery and deduplication; signature verification alone must never issue a product.
Before enabling customer payments
- Test a valid signature, altered body, stale timestamp and unknown key.
- Test a duplicate event and a receiver crash during fulfilment.
- Test an API timeout after a successful creation: retry the original key.
- Test 429 with Retry-After, nginx HTML errors and temporary 503.
- Review expiry, canceled orders and late transfers with your support team.
Full launch checklist and atomic fulfilment example · API errors · Request beta access