FastCoins — это PTC-сеть (Paid-To-Click) для паблишеров. Размести нашу рекламу на своём сайте, твои пользователи получают токены за её просмотр, а ты автоматически получаешь долю дохода.
Создай аккаунт паблишера, подтверди владение доменом и получи API-доступы после проверки.
-
1Создай аккаунт паблишера на FastCoins.com/publisher_dashboard. Понадобятся название сайта и URL.
-
2Добавь домен во вкладке Domains. У каждого домена свои API Key, Secret Key, Bearer Token, настройки токена и URL postback.
-
3Укажи свой postback URL — endpoint на твоём сервере, который принимает уведомления о наградах (см. раздел Postback ниже).
-
4Сгенерируй Bearer Token во вкладке Domains. Он нужен для аутентификации вызовов PTC API.
-
5Отправь тестовый postback из вкладки Domains, чтобы убедиться, что твой endpoint отвечает правильно, прежде чем запускаться вживую.
Сервер-к-серверу уведомление, которое отправляется на твой сайт, когда пользователь досматривает рекламу, — так ты можешь начислять награды автоматически, без клиентского кода.
🔐 Требования безопасности
- Разрешённые IP сервера:
62.171.140.250,2a02:c207:2329:8601::1— проверяй это только по$_SERVER['REMOTE_ADDR']. Это прямой вызов сервер-к-серверу, а не запрос браузера, между нами нет нашего прокси/CDN; заголовок вродеX-Forwarded-Forподделать тривиально, поэтому для этой проверки ему доверять нельзя. userIpобязателен, это не просто справочное поле, — отклоняй callback, если он отсутствует или не является корректным IP. Мы всегда резолвим и отправляем реальный IP просмотревшего рекламу (через Cloudflare на нашей стороне) именно для того, чтобы ты мог опираться на него при антифроде, лимитах по IP и проверках дубликатов пользователей, — endpoint, который его игнорирует, полностью теряет этот сигнал.- Обязательный HTTP-ответ: ровно
ok— строчными, без пробелов, без HTML, HTTP 200 - Алгоритм подписи (рекомендуемый):
signature_sha256=HMAC-SHA256(subId + transId + reward, secret_key)—secret_keyэто ключ HMAC, он не дописывается к сообщению.userIpв нём не участвует. - Алгоритм подписи (старый, по-прежнему отправляется в каждом callback):
signature=MD5(subId + transId + reward + secret_key)— значения склеены без разделителей. Сохранён бессрочно для интеграций, созданных до появленияsignature_sha256; менять ничего не нужно, если ты уже проверяешь эту подпись. - Нужно больше 8 знаков после запятой? Каждый callback также несёт
reward_precise(10 знаков) со своей подписьюsignature_sha256_precise=HMAC-SHA256(subId + transId + reward_precise, secret_key)— полностью опционально и аддитивно, считается так же, просто по более точной строке.reward/signature_sha256/signatureотправляются ровно как раньше, используешь ты это или нет. - Защита от дублей: всегда проверяй
transIdв своей БД перед начислением
📋 Параметры postback (GET)
Твой postback URL получает эти параметры по HTTP GET.
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
| subId | string | ОБЯЗАТЕЛЬНО | Уникальный ID пользователя в твоей системе — значение, которое ты передал в вызове виджета/API |
| transId | string | ОБЯЗАТЕЛЬНО | Уникальный ID транзакции — всегда проверяй его, чтобы не начислять дважды |
| reward | float | ОБЯЗАТЕЛЬНО | Награда в твоей токен-валюте (уже сконвертирована по настроенному token_rate), с 8 знаками после запятой |
| reward_precise | float | Опционально | Та же награда, что и reward, но с 10 знаками после запятой вместо 8 — используй, когда 8 знаков тебе не хватает |
| reward_name | string | ОБЯЗАТЕЛЬНО | Название токена, настроенное для твоего домена (напр. POINTS, COINS) |
| reward_value | float | ОБЯЗАТЕЛЬНО | То же, что reward — добавлено для совместимости с bitcotasks |
| offer_name | string | Опционально | Заголовок просмотренной рекламы — удобно для логов |
| offer_type | string | Опционально | Всегда ptc для просмотров рекламы FastCoins |
| payout | float | Опционально | То же, что reward — добавлено для совместимости со сторонними системами |
| userIp | string | ОБЯЗАТЕЛЬНО | Реальный IP-адрес пользователя, выполнившего оффер, резолвится через Cloudflare на нашей стороне до отправки этого callback, — отклоняй callback, если он отсутствует или не проходит FILTER_VALIDATE_IP. Используй для антифрода, лимитов по IP, выявления дубликатов пользователей, аудита конверсий и аналитики. |
| status | integer | ОБЯЗАТЕЛЬНО | 1 = Начислить награду | 2 = Возврат (вычесть награду) |
| signature_sha256 | string | ОБЯЗАТЕЛЬНО | HMAC-SHA256 от subId + transId + reward, где ключом служит secret_key — рекомендуем для новых интеграций |
| signature_sha256_precise | string | Опционально | HMAC-SHA256 от subId + transId + reward_precise, где ключом служит secret_key — проверяй эту подпись вместо signature_sha256, только если читаешь и reward_precise |
| signature | string | ОБЯЗАТЕЛЬНО | Старый: MD5 от subId + transId + reward + secret_key, склеенных (без разделителей). По-прежнему отправляется в каждом callback без изменений — сохранён для интеграций, созданных до появления signature_sha256. |
| test | integer | Опционально | 1 = тестовый postback из кабинета — реальные награды не начисляй |
🧮 Пример: построение и проверка подписанного URL
Полностью разобранный пример, чтобы ты мог сверить свой расчёт подписи с нашим до запуска вживую. Секретный ключ здесь выдуманный, чисто для наглядности.
Secret Key (example only, never a real one): demo_secret_do_not_use_9f8e7d6c Parameters for this conversion: subId = user_12345 transId = txn_987654321 reward = 0.50 status = 1 userIp = 127.0.0.1 signature_sha256 = HMAC-SHA256(subId + transId + reward, secret_key) = HMAC-SHA256("user_12345" . "txn_987654321" . "0.50", demo_secret_do_not_use_9f8e7d6c) = HMAC-SHA256("user_12345txn_9876543210.50", demo_secret_do_not_use_9f8e7d6c) = df3eafaa3e8cf4fe8fb40c7bed633f34bc7b964bca58d2d691314771a042d59c signature (legacy) = MD5(subId + transId + reward + secret_key) = MD5("user_12345" . "txn_987654321" . "0.50" . "demo_secret_do_not_use_9f8e7d6c") = MD5("user_12345txn_9876543210.50demo_secret_do_not_use_9f8e7d6c") = d1eb68e41cbd5770de9384c5be8ebb39 Resulting callback URL: https://yoursite.com/postback.php?subId=user_12345&transId=txn_987654321&reward=0.50&status=1&userIp=127.0.0.1&signature=d1eb68e41cbd5770de9384c5be8ebb39&signature_sha256=df3eafaa3e8cf4fe8fb40c7bed633f34bc7b964bca58d2d691314771a042d59c
reward_name, reward_value, offer_name, offer_type, payout и test ради краткости — в реальном callback есть все они, см. таблицу выше.📐 Опционально: точность 10 знаков (reward_precise)
Тот же секрет, тот же callback — просто награда с большей точностью, чем вмещают 8 знаков reward. Чисто аддитивно: пропусти, если 8 знаков покрывают твой случай.
Same secret key and subId/transId as above, but the underlying reward this time carries more precision: reward = 0.12345679 (8 decimals, rounded) reward_precise = 0.1234567890 (10 decimals) signature_sha256_precise = HMAC-SHA256(subId + transId + reward_precise, secret_key) = HMAC-SHA256("user_12345" . "txn_987654321" . "0.1234567890", demo_secret_do_not_use_9f8e7d6c) = 219d2101a528b8585161bd26c3bb80c5de8a6741d60d2f1778eb805a05f7a707
💻 Реализация на PHP — postback.php
Сохрани это на своём сервере, укажи свой Secret Key из вкладки Domains, затем вставь полный URL в поле Postback URL своего домена.
// ── 1. YOUR SECRET KEY (from Domains tab → Secret Key) ───── $secret = ""; // ← paste your secret key here // ── 2. IP VALIDATION (callback source) ────────────────────── // REMOTE_ADDR only — this call comes straight from our server to // yours, no browser and no proxy of ours in between, so it's the // one value here that can't be spoofed by whoever is calling you. // X-Forwarded-For (or any other header) is attacker-controlled and // must never be trusted for this check. $allowed_ips = ['62.171.140.250', '2a02:c207:2329:8601::1']; $ip = $_SERVER['REMOTE_ADDR'] ?? ''; if (!in_array($ip, $allowed_ips, true)) { echo "ERROR: Invalid source IP"; exit; } // ── 3. COLLECT PARAMETERS ─────────────────────────────────── $userId = $_GET['subId'] ?? null; $transId = $_GET['transId'] ?? null; $status = $_GET['status'] ?? null; $isTest = isset($_GET['test']) && $_GET['test'] == 1; $userIp = $_GET['userIp'] ?? null; // real IP of the ad viewer — required, see step 4 // ── 3b. DYNAMIC PRECISION ──────────────────────────────────── // Prefer reward_precise (10 decimals) when the callback carries it; // fall back to the standard reward (8 decimals) otherwise. Whichever // pair is picked, it's verified against its own matching signature in // step 6 — if that signature is valid, the postback is accepted. $hasPrecise = isset($_GET['reward_precise'], $_GET['signature_sha256_precise']); $reward = $hasPrecise ? $_GET['reward_precise'] : ($_GET['reward'] ?? null); $signature = $hasPrecise ? $_GET['signature_sha256_precise'] : ($_GET['signature_sha256'] ?? null); // ── 4. VALIDATE USER IP (required) ────────────────────────── // userIp is mandatory — this is the real IP of the person who // completed the offer, already resolved through Cloudflare on our // end. Reject the callback outright rather than silently accepting // one without it, since your fraud detection / IP-limit / duplicate- // user checks depend on this value actually being present and valid. if (!$userIp || !filter_var($userIp, FILTER_VALIDATE_IP)) { echo "ERROR: Invalid user IP"; exit; } // ── 5. VALIDATE REQUIRED PARAMS ───────────────────────────── if (!$userId || !$transId || $reward === null || !$signature) { echo "ERROR: Missing parameters"; exit; } // ── 6. VERIFY SIGNATURE ───────────────────────────────────── // Only subId + transId + reward go into this hash — userIp is NOT // part of it. $secret is the HMAC key, not concatenated into the // message. Don't add userIp here unless a different provider's docs // explicitly say their signature scheme includes it. $expected = hash_hmac('sha256', $userId . $transId . $reward, $secret); if (!hash_equals($expected, $signature)) { echo "ERROR: Signature mismatch"; exit; } // ── 7. HANDLE CHARGEBACK (status = 2) ─────────────────────── if ((int)$status === 2) { $reward = -abs((float)$reward); // negative = deduct } // ── 8. TEST CALLBACK ───────────────────────────────────────── // Authenticated and validated like any other callback, but must not // touch a real balance. if ($isTest) { echo "ok"; exit; } // ── 9. PREVENT DUPLICATES, CREDIT REWARD & STORE USER IP ────── // Replace with your actual DB logic. if (!isTransactionProcessed($transId)) { creditUserReward($userId, $reward, $transId); // If your credit function only takes these 3 args, store $userIp // with the conversion separately, e.g.: // saveConversionIp($transId, $userId, $userIp); } // ── 10. REQUIRED RESPONSE ─────────────────────────────────── echo "ok"; // MUST return exactly this, nothing else
🕓 Старая проверка — MD5
signature через MD5? Он по-прежнему приходит в каждом callback, полностью без изменений, — этот блок чисто для справки. Действий не требуется. Новым интеграциям лучше использовать signature_sha256 выше.$signature = $_GET['signature'] ?? null; $expected = md5($userId . $transId . $reward . $secret); if (!hash_equals($expected, $signature)) { echo "ERROR: Signature mismatch"; exit; } // ...same IP check, userIp check, and crediting logic as the main example above.
🔀 Схема обработки callback
- 1Callback получен
- 2Сверь
REMOTE_ADDRс белым списком IP - 3Прочитай параметры, включая
userIp - 4Проверь
userIp— отклони, если отсутствует или не является корректным IP - 5Проверь остальные обязательные параметры
- 6Проверь подпись
- 7Проверь
status— при2проведи как возврат - 8Если
test=1, верниok, ничего не начисляя - 9Сверь
transIdсо своей БД, чтобы не начислять дубли - 10Начисли или отмени награду и сохрани
userIpвместе с конверсией - 11Верни
ok
Возможные ответы:
ok— callback обработан (или признан тестовым)ERROR: Invalid source IPERROR: Invalid user IPERROR: Missing parametersERROR: Signature mismatch
ok — без пробелов, без переводов строки, без HTML.transId.test=1 — награды за них не начисляй.Запрашивай доступную PTC-рекламу через REST API и отрисовывай её в собственном интерфейсе. Встраивать виджет не нужно.
🌐 Endpoint
| Сегмент | Описание |
|---|---|
| API_KEY | API Key твоего домена — находится во вкладке Domains твоего кабинета паблишера |
| USER_ID | Уникальный идентификатор пользователя в твоей системе. Возвращается как subId в postback |
| USER_IP | Реальный IP-адрес пользователя — используется для таргетинга рекламы и защиты от фрода. Передавай настоящий IP клиента |
| DEVICE | Ровно desktop или mobile — третьего значения нет. Планшеты (iPad, Android-планшеты) считаются mobile. Реальное устройство посетителя обязательно, как только у какого-либо рекламодателя появится кампания с таргетингом по устройствам: ты вызываешь этот API со своего сервера, User-Agent твоего сервера — не устройство посетителя, мы не можем определить его за тебя, — ты обязан определить и передать его сам. Всё, что не desktop/mobile (включая буквальное "tablet"), считается как отсутствие значения — приводи к mobile самостоятельно. Запрос без этого параметра или с опечаткой в таком случае получает HTTP 400. |
[DEVICE], добавь его сейчас. Сейчас он молча игнорируется, но как только таргетинг по устройствам заработает, начнёт возвращаться HTTP 400 — не жди, пока это сломает твой фид.🔑 Аутентификация — Bearer Token
Authorization. Генерируй его для каждого домена во вкладке Domains (кнопка 🔄 рядом с "Bearer Token").💻 Пример PHP
// ── YOUR CREDENTIALS (from Domains tab) ───────────────────── $apiKey = 'YOUR_API_KEY'; $bearerToken = 'YOUR_BEARER_TOKEN'; // ── USER DATA ──────────────────────────────────────────────── $userId = $_SESSION['user_id']; // your user's unique ID // REMOTE_ADDR by default — it's the one value here that can't be // spoofed by the visitor. Only read a proxy header instead (e.g. // CF-Connecting-IP) if your own server sits behind a reverse proxy // you control and trust — never a client-supplied header like // X-Forwarded-For, which the visitor's own browser can set to // anything. $userIp = $_SERVER['REMOTE_ADDR'] ?? ''; // Required once any advertiser runs a device-targeted campaign — this // script call happens during the visitor's own page load, so THIS // server's incoming User-Agent is theirs. Don't skip this: a call from a // background job/cron with no real visitor attached has no correct value // to send here. $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? ''; $device = preg_match('/Mobi|Android|iPhone|iPod|iPad|BlackBerry|IEMobile|Opera Mini/i', $userAgent) ? 'mobile' : 'desktop'; // ── BUILD URL ──────────────────────────────────────────────── $url = 'https://FastCoins.com/api/' . $apiKey . '/' . urlencode($userId) . '/' . urlencode($userIp) . '/' . $device; // ── CURL REQUEST ───────────────────────────────────────────── $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $bearerToken, 'Accept: application/json', ], ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $data = json_decode($response, true); // ── HANDLE RESPONSE ────────────────────────────────────────── if ($httpCode === 200 && $data['status'] === '200') { foreach ($data['data'] as $ad) { // $ad['url'] already contains the user subId encoded echo '<a href="' . htmlspecialchars($ad['url']) . '" target="_blank">'; echo htmlspecialchars($ad['title']); echo ' — ' . $ad['reward'] . ' ' . $ad['currency_name']; echo '</a><br>'; } } else { echo 'No ads available.'; }
📦 Формат ответа
Успешные ответы возвращают HTTP 200 с такой JSON-структурой:
| Поле | Тип | Описание |
|---|---|---|
| id | string | Уникальный идентификатор объявления |
| image | string | Полный URL баннера объявления — может быть пустым |
| title | string | Заголовок для показа — покажи его своим пользователям |
| description | string | Краткое описание объявления |
| duration | string | Требуемое время просмотра в секундах: 2, 30 или 60 |
| reward | float | Сумма награды в токен-валюте твоего домена (уже сконвертирована) |
| currency_name | string | Название токена, настроенное для твоего домена (напр. COINS, USDT) |
| url | string | Перенаправляй пользователя сюда для просмотра рекламы. Postback срабатывает автоматически по завершении. subId пользователя уже закодирован в URL. |
| boosted_campaign | boolean | Является ли объявление продвигаемым — стоит выделять такие в своём интерфейсе |
| ad_type | string | Iframe = проигрывается внутри фрейма | Redirect = пользователь переходит на страницу рекламодателя |
⚠️ Коды ошибок
Ошибки используют ту же JSON-структуру с полем статуса, отличным от 200.
| HTTP | status | Причина и решение |
|---|---|---|
| 401 | 401 | Неверный API key — проверь сегмент API_KEY в URL, либо отсутствует Bearer Token, когда домен его требует |
| 403 | 403 | Bearer Token не совпадает — сгенерируй заново и обнови во вкладке Domains |
| 400 | 400 | Неверный формат USER_ID — только буквы и цифры (a-z, 0-9, _, -, .), максимум 255 символов |
| 400 | 400 | Сегмент DEVICE отсутствует или неверен — возвращается только после включения таргетинга по устройствам. Должен быть ровно desktop или mobile |
| 500 | 500 | Внутренняя ошибка сервера — в поле message детали для отладки. Если повторяется, напиши в поддержку. |
Полный путь: от вызова API до начисления награды на вашем сайте.
📡 Схема API + Postback
GET /api/…url объявленияGET postback.phpok🧩 Встраиваемый виджет (альтернатива)
Если не хочешь использовать API, вставь виджет как iframe — он сам делает всё, со стилями, изолированными от остальной страницы.
<iframe src="https://fastcoins.click/widget-embed.php?api_key=YOUR_API_KEY&sub_id={USER_ID}" style="width:100%;height:640px;border:none;" loading="lazy"></iframe>
YOUR_API_KEY на API Key своего домена и {USER_ID} на ID текущего пользователя (рендерится на сервере). Виджет показывает доступные объявления и задания прямо на странице и автоматически отправляет postback по завершении. Поменяй 640px в style, чтобы сделать iframe выше или ниже под свой макет.sub_id (= {USER_ID}) на реального пользователя — никаких суффиксов для размножения. Каждый твой пользователь должен идентифицироваться одним стабильным id. Формат выбираешь ты — непрозрачный id, логин, email (напр. ABD01230A23, [email protected]) — но одному реальному человеку соответствует ровно один id, навсегда. Не допускается: брать базовый id и дописывать числовой суффикс, превращая одного реального человека в нескольких "пользователей". Детекция не зависит от разделителя — она отрезает завершающие
-, _ или . с цифрами и считает остаток личностью пользователя. Любая из этих пар отклоняется: 123-1&123-2ABC_1&ABC_2A23-23&A23-24order.1&order.2
ABD01230A23, 1001, [email protected] — всё это допустимо). При обнаружении шаблона API всё равно отвечает
ok, но с сообщением, что такой id не разрешён, и награда не начисляется; попытка логируется как фрод. Создай задание (напр. "зарегистрируйся на моём сайте"), пополни счёт на N выполнений и подтверждай каждое подписанным сервер-к-сервер postback. Пользователю автоматически начисляется награда в момент прихода твоего postback — без ручной модерации и без задержек платформы.
📡 Как это работает
/create-taskcka_cid1) URL postback
GET https://fastcoins.click/webhooks/task-postback.php?cka_cid={CLICK_ID}&sign={SIGN}cka_cid— click id, который мы дописали к твоей целевой ссылке, когда пользователь начал (your-url?cka_cid=…). Верни его без изменений.sign—hash_hmac('sha256', $cka_cid, $task_secret)(hex в нижнем регистре). Секрет каждого задания показан в Create Task → My Tasks.
2) Подпись
// PHP
$sign = hash_hmac('sha256', $cka_cid, $task_secret);
// Node.js
const sign = crypto.createHmac('sha256', taskSecret).update(ckaCid).digest('hex');
# Python
import hmac, hashlib
sign = hmac.new(task_secret.encode(), cka_cid.encode(), hashlib.sha256).hexdigest()403, даже с верной подписью. Это плюс HMAC — вся модель подлинности. Береги свой секрет.3) Опциональный аналитический пиксель
Поставь это на страницу конверсии для собственной аналитики. Он никогда не начисляет — postback выше единственное, что начисляет.
<img src="https://fastcoins.click/task-pixel.php?cid={CLICK_ID}" width="1" height="1" alt="">Рекомендации для надёжной, безопасной и доходной интеграции.
⚡ Производительность
- Кэшируй ответы API минимум на 60 секунд на пользователя. Не дёргай API при каждой загрузке страницы — объявления меняются максимум раз в несколько минут.
- Передавай реальный IP пользователя. По умолчанию используй
REMOTE_ADDR— это единственное значение, которое посетитель не может подделать. Читай прокси-заголовок (напр.CF-Connecting-IP) только если твой сервер стоит за обратным прокси, который ты контролируешь и которому доверяешь; никогда не доверяй клиентским заголовкам вродеX-Forwarded-For. Неверные IP снижают качество таргетинга. - Ставь таймаут 10 секунд на cURL-вызовы, чтобы медленный ответ нашего API не тормозил загрузку страниц.
🔒 Безопасность
- Никогда не раскрывай свой Secret Key в клиентском коде и публичных репозиториях. Он используется только на сервере для проверки подписей.
- Всегда сверяй
transIdсо своей базой перед начислением — это защищает от двойных наград из-за случайных повторных отправок postback. - Проверяй IP источника перед обработкой любого postback. Принимай запросы только от
62.171.140.250или2a02:c207:2329:8601::1. - Используй HTTPS для своего postback endpoint. HTTP-endpoints могут блокироваться.
- Отправляй один уникальный
sub_idна реального пользователя. Не добавляй числовые суффиксы к базовому id (123-1/123-2,ABC_1/ABC_2,order.1/order.2) — любой разделитель (-_.) распознаётся как фрод, и запрос отклоняется без начисления. См. правило выше.
💰 Доход
- Продвинутые объявления (
boosted_campaign: true) платят больше — выделяй их в интерфейсе, чтобы поднять кликабельность. - Длинная реклама (60 с) платит заметно больше короткой (2 с). Ставь её в приоритет при высокой вовлечённости пользователей.
- Балансируй долю пользователя — выше выплата привлекает больше активных пользователей; ниже выплата — больше твоя маржа.
- Выводы открыты с 1 по 3 число каждого месяца — отметь в календаре.
🐛 Отладка
- Используй кнопку Test Postback во вкладке Domains, чтобы убедиться, что endpoint возвращает ровно
okс HTTP 200. - Загляни во вкладку Postback Logs — там полные HTTP-ответы каждой попытки postback: коды статусов и ответы сервера.
- Неудавшиеся postback можно отправить повторно по одному или пакетно из вкладки логов. Данные никогда не теряются безвозвратно.
- Если подпись всё время не сходится: для
signature_sha256проверь, чтоsecret_keyпередаётся как ключ HMAC (не дописывается к сообщению) и что ты хэшируешьsubId + transId + rewardбез разделителей; для старогоsignature— что склеиваешьsubId + transId + reward + secretбез разделителей. В обоих случаях убедись, чтоrewardв точности совпадает со строкой из GET-параметра (без округления). - Если проверяешь
signature_sha256_precise: хэш считается поreward_precise, а не поreward— перепутать поля с чужой подписью здесь самая частая ошибка.
Готов начать зарабатывать?
Создай аккаунт паблишера меньше чем за 2 минуты и подтверди свой домен, чтобы начать.
Открыть кабинет паблишера →