Your server gets the signal.

Dedyx delivers signed PAID events to your required HTTPS endpoint. Verify first, then fulfil.

Configure an endpoint

PUT/api/v1/merchant/webhook

Rate limit: 10 requests / 60 seconds (shared webhook settings · per merchant). Shared counters and additional IP limits.

cURL
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.

GET/api/v1/merchant/webhook

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

POST · example payload
{
  "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

HeaderPurpose
X-Dedyx-TimestampUnix seconds; refreshed for each attempt.
X-Dedyx-Signaturev1= + hex HMAC-SHA256 of timestamp + "." + raw body.
X-Dedyx-Event-IDStable event ID across retries.
X-Dedyx-Key-IDSelects your saved signing key.
Python · signature verification
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

POST/api/v1/merchant/webhook/rotate-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.

Next: Errors & limits

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.