Skip to main content

Overview

Payout webhooks allow Payviox to send real-time HTTP POST notifications to your server when payout status changes occur. This enables you to keep your application synchronized with payout statuses without polling the API.
Payout webhooks use a separate URL and secret token from payment webhooks. Configure your payout webhook URL in the Payviox Dashboard under Payout > Webhooks.

Configuration

1

Create Your Endpoint

Create an endpoint on your server to receive POST requests (e.g., https://yourdomain.com/api/payout-webhook)
2

Add URL to Dashboard

Go to your Payviox Dashboard and navigate to Payout > Webhooks
3

Enter Your Payout Webhook URL

Paste your endpoint URL in the payout webhook URL field
4

Save Your Payout Webhook Token

Copy your payout webhook token - you’ll need it to verify webhook signatures
5

Select Payout Events

Choose which payout events you want to receive. By default, only payout.succeeded is enabled.
6

Enable Webhooks

Toggle the payout webhook switch to enable delivery.

Available Events

You can subscribe to the following payout webhook events:
EventDescriptionRecommended Action
payout.createdPayout order has been createdUpdate status to “processing”
payout.processingPayout is being sentInform the recipient that the payout is on its way
payout.succeededPayout confirmed and deliveredConfirm delivery to the recipient
payout.failedPayout failedContact support or retry
payout.rejectedPayout was rejectedInform the recipient of the rejection
Always verify webhook signatures to ensure requests are coming from Payviox and not from malicious actors.

Payload Structure

All payout webhooks follow the same root layout: amounts, metadata, type, provider, your internal order_id, recipient, and exactly one extra object whose JSON key matches provider (paypal, crypto, or any future rail). That object carries provider-specific identifiers and details only—no duplicate amounts. Some event types add optional root fields (e.g. reason on payout.rejected when an admin note exists). Omitted fields are not sent.

Reference payload

Example for payout.succeeded with provider paypal:
{
  "amount": 1000,
  "currency": "USD",
  "fees": 20,
  "net_amount": 980,
  "metadata": {
    "payout_user_id": "699af8215799c577cb997eb1"
  },
  "type": "payout.succeeded",
  "provider": "paypal",
  "order_id": "679abc1234def567890abcde",
  "recipient": {
    "email": "sam2@gmail.com"
  },
  "paypal": {
    "order_id": "PP-5MZZ9QME1MLW",
    "email": "g@gmail.com"
  }
}
The crypto object replaces paypal. Field set may evolve with the rail; amounts stay on the root.
{
  "amount": 5000,
  "currency": "USD",
  "fees": 150,
  "net_amount": 4850,
  "metadata": {
    "payout_user_id": "abc123"
  },
  "type": "payout.succeeded",
  "provider": "crypto",
  "order_id": "679def5678abc901234def56",
  "recipient": {
    "email": "user@example.com"
  },
  "crypto": {
    "order_id": "OKX-ABCDEF123456",
    "currency": "USDT",
    "chain": "ERC20",
    "crypto_amount": 49.50,
    "usd_amount": 50.00,
    "to_address": "0x1234...",
    "tx_hash": "0xabc...",
    "confirmations": 12,
    "network_fee": 0.002
  }
}
When a payout is rejected (e.g. PayPal manual review), type is payout.rejected. reason is included only if the operator entered a note (same text as in the dashboard).
{
  "amount": 1000,
  "currency": "USD",
  "fees": 20,
  "net_amount": 980,
  "metadata": {
    "payout_user_id": "699af8215799c577cb997eb1"
  },
  "type": "payout.rejected",
  "provider": "paypal",
  "order_id": "679abc1234def567890abcde",
  "recipient": {
    "email": "sam2@gmail.com"
  },
  "paypal": {
    "order_id": "PP-PENDING-EXAMPLE",
    "email": "g@gmail.com"
  },
  "reason": "Payout request does not match our verification requirements."
}

Payload Fields

amount
integer
required
The payout amount in the smallest currency unit (e.g., cents for USD)
currency
string
required
Three-letter ISO currency code (always USD for payouts)
fees
integer
Fees amount in the smallest currency unit. Omitted if zero.
net_amount
integer
Amount after fees in the smallest currency unit. Omitted if equal to amount.
metadata
object
required
Metadata associated with the payout
type
string
required
The payout event type. Possible values:
  • payout.created: Payout order has been created.
  • payout.processing: Payout is being sent.
  • payout.succeeded: Payout confirmed and delivered.
  • payout.failed: Payout failed.
  • payout.rejected: Payout was rejected.
reason
string
Only on payout.rejected. Human-readable rejection note entered by the admin (PayPal payouts). Omitted if no note was provided.
provider
string
required
The payout provider: "paypal" or "crypto"
order_id
string
required
Payviox internal order identifier
recipient
object
required
Recipient information
paypal
object
PayPal-specific details. Only present when provider is "paypal".
crypto
object
Crypto-specific details. Only present when provider is "crypto".
test_mode
boolean
Only present on test webhooks sent from the dashboard. Always true when present.

Webhook Headers

Every payout webhook request includes these headers:
HeaderDescription
Content-TypeAlways application/json
SignatureHMAC SHA256 signature for verification

Signature Verification

Critical Security Step: Always verify the webhook signature before processing the payload. This ensures the request is legitimate and from Payviox.
The Signature header contains an HMAC SHA256 hash of the request body, signed with your payout webhook token (not the payment webhook token).

Verification Algorithm

  1. Get the raw request body (JSON string)
  2. Compute HMAC SHA256 hash using your payout webhook token as the secret key
  3. Compare the computed signature with the Signature header
  4. Only process the webhook if signatures match

Implementation Examples

<?php

function verifyPayoutWebhookSignature($payload, $receivedSignature, $payoutWebhookToken) {
    $computedSignature = hash_hmac('sha256', $payload, $payoutWebhookToken);
    return hash_equals($computedSignature, $receivedSignature);
}

public function handlePayoutWebhook(Request $request) {
    $payload = $request->getContent();
    $receivedSignature = $request->header('Signature');
    $payoutWebhookToken = env('PAYVIOX_PAYOUT_WEBHOOK_TOKEN');

    if (!verifyPayoutWebhookSignature($payload, $receivedSignature, $payoutWebhookToken)) {
        return response()->json(['error' => 'Invalid signature'], 401);
    }

    $data = json_decode($payload, true);

    switch ($data['type']) {
        case 'payout.created':
            $this->markPayoutProcessing($data['order_id']);
            break;
        case 'payout.processing':
            $this->notifyRecipientInTransit($data['order_id']);
            break;
        case 'payout.succeeded':
            $this->confirmPayoutDelivery($data['order_id'], $data);
            break;
        case 'payout.failed':
            $this->handlePayoutFailure($data['order_id'], $data);
            break;
        case 'payout.rejected':
            $this->handlePayoutRejection($data['order_id'], $data);
            break;
    }

    return response()->json(['status' => 'success'], 200);
}

Retry Logic

Payviox implements an automatic retry mechanism for failed payout webhook deliveries:
1

First Attempt

Instant delivery (0 seconds)
2

Second Attempt

30 seconds after first failure
3

Third Attempt

5 minutes after second failure
4

Fourth Attempt

30 minutes after third failure
After 4 failed attempts, the webhook will be marked as failed. You can manually retry failed webhooks from the Payviox Dashboard under Payout > Webhook Logs.

Success Criteria

A webhook is considered successfully delivered when your endpoint:
  • Returns HTTP status code 200
  • Responds within 30 seconds

Testing

You can send a test payout webhook from the dashboard to verify your integration:
  1. Go to Payout > Webhooks in the dashboard
  2. Click Send Test Webhook
  3. Select the event type to test
  4. The test payload will include "test_mode": true
Test webhooks use the same signature mechanism as production webhooks. Use them to verify your signature verification logic.

Best Practices

Troubleshooting

Possible causes:
  • Using the payment webhook token instead of the payout webhook token
  • Parsing/modifying the request body before verification
  • Incorrect HMAC algorithm (must be SHA256)
Solution: Ensure you are using the payout webhook token from the dashboard. Always verify against the raw request body before any parsing.
Possible causes:
  • Payout webhooks are not enabled (toggle is off)
  • Incorrect payout webhook URL in dashboard
  • Not subscribed to the event type
  • Firewall blocking Payviox IP addresses
Solution: Check that payout webhooks are enabled, the URL is correct, and you are subscribed to the relevant events.
Possible causes:
  • Not implementing idempotency checks
  • Slow response times causing retries
Solution: Implement idempotency using order_id + type as a unique key. Respond quickly with 200 OK and process asynchronously.
Possible causes:
  • Processing taking too long before responding
  • Database locks or slow queries
Solution: Return 200 OK immediately and process webhooks asynchronously in a background queue.

Need Help?

If you’re having trouble with payout webhook integration:
  • Check the Payout Webhook Logs in your dashboard
  • Review your server logs for errors
  • Contact support@payviox.com for assistance
Use the Send Test Webhook feature in the dashboard to quickly verify your integration before going live.