Your server gets the signal.
Dedyx delivers signed PAID events to your required HTTPS endpoint. Verify first, then fulfil.
Configure an endpoint
Rate limit: 10 requests / 60 seconds (shared webhook settings · per merchant). Shared counters and additional IP limits.
curl -X PUT https://api.dedyx.com/api/v1/merchant/webhook \
-H 'X-API-Key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"url":"https://your-company.example/webhooks/dedyx"}'Use a public HTTPS URL on port 443 without embedded credentials or a fragment. Private networks, loopback, metadata addresses, HTTP and redirects are rejected. No global URL allowlist is required.
Your first response includes a merchant-specific signing_key and key_id. Save the full whsec_... string. Later PUT requests update the URL and retain the existing key.
Rate limit: 120 requests / 60 seconds (shared reads · per merchant). Shared counters and additional IP limits.
Read the current settings without exposing the signing secret.
Payment event
{
"event_id": "evt_example",
"payment_id": "pay_0123456789abcdef0123456789abcdef",
"status": "PAID",
"expected_amount": "49.00",
"transaction_id": "9f3c7a2e8b104d65c0a9f1726e83b4d5a7610c2f94e8b36d5a07c1e2694f83bd",
"transaction_event_index": 0,
"description": "Order #1024"
}Match the payment to your own order records. Multiple USDT Transfer events may share a transaction ID; transaction_event_index identifies the position in the full receipt log.
Verify the signature
| Header | Purpose |
|---|---|
X-Dedyx-Timestamp | Unix seconds; refreshed for each attempt. |
X-Dedyx-Signature | v1= + hex HMAC-SHA256 of timestamp + "." + raw body. |
X-Dedyx-Event-ID | Stable event ID across retries. |
X-Dedyx-Key-ID | Selects your saved signing key. |
import hashlib
import hmac
import time
def verify_webhook(raw_body, timestamp, signature, signing_key):
if not timestamp.isdigit() or len(timestamp) > 12:
return False
if abs(int(time.time()) - int(timestamp)) > 300:
return False
expected = "v1=" + hmac.new(
signing_key.encode(),
timestamp.encode() + b"." + raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)Verify against the raw request bytes before parsing JSON, using the entire signing key string. Then validate the order, amount, status and matching header/payload event IDs.
Handle retries and duplicates
Only 2xx means delivery succeeded. Requests can repeat if a response is lost. Atomically save event_id with your business action. Return 2xx without repeating fulfilment for an already processed event. Working PostgreSQL example.
Delivery normally retries up to eight times with backoff. Exhausted events remain available for authenticated merchant retry or operator recovery. A failed webhook does not undo the payment. Reconcile through authenticated payment details; Inspect delivery with GET /api/v1/payments/{payment_id}/webhooks and retry an exhausted event with POST /api/v1/merchant/webhook/events/{event_id}/retry. Pending and delivered events cannot be retried. The retry limit is five requests per hour per merchant; each retry is audited and retains its event ID and payload.
Rotate the signing key
Rate limit: 10 requests / 60 seconds (shared webhook settings · per merchant). Shared counters and additional IP limits.
The new secret is shown once. New delivery attempts immediately use the new key ID. Keep the previous key available for five minutes for requests already in flight. This rotation does not change your API key.
Delivery diagnostics and retry
Read your payment events with GET /api/v1/payments/{payment_id}/webhooks. The response is a bounded list (up to 100 events) with event_id, payment_id, status, attempts, timestamps and error_category. Status is pending, delivered or failed; pending also includes an in-flight attempt. No callback URL, payload or signing secret is returned. This endpoint shares the merchant read limit.
Use POST /api/v1/merchant/webhook/events/{event_id}/retry with X-API-Key and no body to start a new cycle for an exhausted undelivered event. Pending/delivered returns 409; another merchant’s or missing event returns 404. Five requests per fixed 3600-second window per merchant (starting at the first request), including 404 and 409 responses; 429 includes Retry-After. Attempts reset to zero for the new cycle. Event ID and payload stay unchanged. Delivery uses your current receiver and signing key. Audit and retry are atomic; no new payment or creation quota is used.
A timeout can mean that your receiver processed the event but its response was lost. Deduplicate event_id and fulfillment by order even after a manual retry.