FP-001
Send a FaucetPay payout with the v1 API
A minimal, correct call to FaucetPay's /api/v1/send endpoint, including the part almost everyone gets wrong: the amount is in the coin's smallest unit, not USD.
FaucetPay's send endpoint is a plain form POST. There is no JSON body, no bearer token, and no SDK to install. What trips people up is the amount field.
FaucetPay pays in the coin's smallest unit. For most coins that is the satoshi, meaning 100,000,000 units to one coin. If your site keeps balances in USD you have to convert before you call this, and if you get the conversion backwards you will send a hundred million times too much.
The function below returns the decoded response array on success and throws on transport failure, so your payout queue can tell the difference between "FaucetPay said no" and "the request never arrived". That distinction matters: the first is final, the second should be retried.
function faucetpay_send(string $apiKey, string $to, string $currency, int $units, ?string $refId = null): array
{
$post = [
'api_key' => $apiKey,
'amount' => $units, // smallest unit (satoshi), NOT USD
'to' => $to,
'currency' => strtoupper($currency),
];
if ($refId !== null) {
$post['ref_id'] = $refId; // your own id, useful for reconciliation
}
$ch = curl_init('https://faucetpay.io/api/v1/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($post),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 20,
CURLOPT_CONNECTTIMEOUT => 8,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_FOLLOWLOCATION => false,
]);
$body = curl_exec($ch);
$err = curl_error($ch);
$http = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($body === false) {
// Transport failure. The payout may or may not have happened: retry, do not
// mark it failed, and never credit the user's balance back on this branch.
throw new RuntimeException('faucetpay transport error: ' . $err);
}
$data = json_decode((string) $body, true);
if (!is_array($data)) {
throw new RuntimeException('faucetpay returned non-json (http ' . $http . ')');
}
return $data; // ['status' => 200, 'message' => ..., 'payout_id' => ...]
}
Using it
Call it with units, never dollars:
$res = faucetpay_send($key, $wallet, 'LTC', 12000);
A successful response carries status 200. Anything else is a refusal with a message explaining why: unknown wallet, below minimum, insufficient balance, bad API key.
Wrap the call so a thrown exception leaves the withdrawal in a held state rather than a failed one. Held withdrawals can be retried by hand. Failed ones tend to get refunded twice.
What bites people
FaucetPay does not pay in USD. There is no USD currency code. If your internal ledger is in dollars you must convert to the coin's units yourself, and you should store the rate you used on the payout row.
Sending to an address that is not a registered FaucetPay user fails. Check the address first if you want a clean error message for the user.
Do not retry automatically on a timeout without an idempotency strategy. A ref_id you generate and store lets you check afterwards whether the payout landed.
This one touches real money. Point it at a throwaway wallet and watch a full cycle before you trust it with a live balance.