Dokumentasi API SMM Panel
Semua fungsi panel bisa diakses lewat dua API terpisah. Keduanya memakai akun, saldo, dan katalog yang sama; bedanya ada pada format dan kemampuannya.
Semua fungsi panel bisa diakses lewat dua API terpisah. Keduanya memakai akun, saldo, dan katalog yang sama; bedanya ada pada format dan kemampuannya.
Kenapa ada dua API?
API reseller klasik (v2) yang dipakai seluruh industri mengirim form ke satu endpoint dan selalu menjawab HTTP 200. Format inilah yang diharapkan software panel siap pakai, jadi API ini dibiarkan apa adanya. Namun developer yang membangun sistem sendiri terus terbentur batasannya: error tidak bisa dibedakan satu sama lain, katalog datang dalam satu paket besar, dan status pesanan harus dicek berulang kali tanpa henti. v3 dibuat untuk mereka.
Perbandingan
| Fitur | Legacy (v2) | Baru (v3) |
|---|---|---|
| Bentuk | Satu endpoint, kirim form, parameter action | REST berbasis resource, body JSON |
| Status HTTP | Selalu 200, bahkan saat gagal | Kode yang sebenarnya (400, 401, 402, 404, 409, 429, 502) |
| Error | Teks bebas | type + code yang stabil + pesan sesuai bahasa + param + doc_url |
| Status pesanan | Hanya teks yang diterjemahkan | Nilai mesin yang stabil plus label tampilan terpisah |
| Deskripsi layanan | Tidak ada | Deskripsi dalam 16 bahasa, waktu rata-rata, platform, kategori |
| Field pesanan | Ditebak dari nama tipe | Setiap layanan menerbitkan skema field-nya sendiri |
| Satuan harga | Tidak disebutkan (sumber salah hitung 1.000 kali lipat pada paket) | Tertulis jelas: per_1000 atau per_order |
| Katalog | Semua layanan dalam satu response | Filter plus paginasi cursor |
| Perlindungan duplikat | Tidak ada | Idempotency-Key |
| Pembaruan status | Polling terus-menerus | Webhook bertanda tangan atau event stream |
| Skema | Tidak ada | OpenAPI 3.1 |
| Bahasa | Inggris dan Turki (URL terpisah) | 16 bahasa (lewat header atau parameter) |
| Kualitas layanan | Tidak ada | Skor, tingkat keyakinan, dan bukti per layanan; daftar pendek berperingkat |
Mana yang sebaiknya saya pakai?
Pilih API legacy jika Anda memakai software panel siap pakai, bot, atau panel reseller. Sebagian besar hanya meminta Anda mengganti URL API dan API key, lalu langsung berjalan dalam hitungan menit.
Pilih v3 jika Anda membangun aplikasi, toko online, atau otomasi sendiri. Penanganan error, perlindungan duplikat, dan notifikasi sudah menjadi fitur bawaan, dan form pesanan bisa Anda buat langsung dari skema layanan.
Cara memulai
- 1Buat API key di tab API key.
- 2Ambil daftar layanan, lalu baca id dan skema field layanan yang Anda butuhkan.
- 3Validasi pesanan dulu dengan preview, baru kemudian buat pesanannya.
- 4Daftarkan webhook, atau baca event stream, untuk memantau perubahan status.
REST API yang dirancang untuk developer yang membangun sistem sendiri: path berbasis resource, kode status HTTP yang sebenarnya, error yang bisa dibaca mesin, dan notifikasi bertanda tangan.
Base URL
Setiap path ditambahkan di belakang URL ini. Versi ada di dalam path: jika suatu saat perlu perubahan yang tidak kompatibel, path baru (v4) diterbitkan dan path ini tetap berjalan tanpa diubah. Tanggal rilis kontrak dikirim di header X-Api-Version pada setiap response.
https://panelfollows.com/api/v3Autentikasi
Kirim API key Anda sebagai Bearer token di header Authorization. Header X-Api-Key juga diterima sebagai alternatif.
GET https://panelfollows.com/api/v3/account
Authorization: Bearer pf_live_...API key legacy yang sudah Anda punya juga berfungsi di v3, jadi Anda bisa langsung mencobanya. Untuk production, gunakan API key v3: bisa diberi label, dicabut satu per satu, dan tidak pernah disimpan dalam bentuk teks biasa.
Mulai cepat
curl https://panelfollows.com/api/v3/services?limit=5 \
-H "Authorization: Bearer YOUR_API_KEY"Bahasa
Pilih bahasa response dengan header Accept-Language atau parameter ?lang=. Jika keduanya dikirim, parameter yang dipakai. Nama layanan, deskripsi layanan, nama kategori, label status pesanan, label field pesanan, dan pesan error semuanya dikirim dalam bahasa tersebut.
Nilai mesin tidak pernah berubah mengikuti bahasa: error.code, order.status, service.type, dan currency selalu sama. Jadikan nilai itu dasar logika kode Anda, dan tampilkan teksnya kepada pengguna.
Accept-Language: tr
# veya
GET https://panelfollows.com/api/v3/services?lang=trFormat request dan response
Body request berupa JSON (application/json); form-urlencoded juga diterima untuk tes cepat. Response berupa JSON: satu resource dikirim sebagai objek biasa, sedangkan daftar dibungkus dalam envelope berisi data, has_more, dan next_cursor. Setiap objek membawa field object yang menyebutkan tipenya.
Nominal dikirim sebagai STRING desimal ("1.2340"), bukan float. Parse ke tipe desimal di sisi Anda agar tidak ada pecahan yang hilang. Mata uangnya USD.
Timestamp memakai format RFC 3339 (2026-08-21T00:24:45.255Z).
Error
Kegagalan mengembalikan kode status HTTP yang sebenarnya dan body berisi satu objek error. Logika kode Anda sebaiknya berpatokan pada error.code: nilainya stabil dan tidak pernah berubah mengikuti bahasa.
HTTP/1.1 400 Bad Request
Content-Type: application/json
X-Request-Id: req_0c858d8af7f65eca001b2f5a
{
"object": "error",
"error": {
"type": "invalid_request_error",
"code": "quantity_out_of_range",
"message": "Miktar, bu servisin izin verdiği aralığın dışında.",
"param": "quantity",
"doc_url": "https://panelfollows.com/api-docs#error-quantity_out_of_range",
"request_id": "req_0c858d8af7f65eca001b2f5a"
}
}| type | Kelas umum: apakah bisa dicoba ulang, apakah kesalahan ada di pihak Anda. |
| code | Nilai mesin yang stabil. Jadikan ini dasar percabangan. |
| message | Teks yang bisa dibaca manusia, dalam bahasa pilihan Anda. |
| param | Nama field yang bermasalah, jika ada. |
| doc_url | Link langsung ke bagian dokumentasi yang relevan. |
| request_id | Satu-satunya referensi yang perlu Anda sebutkan saat menghubungi tim dukungan. |
Batas request (rate limit)
600 request per menit per API key, ditambah 900 per menit per IP. Setiap response menyertakan header RateLimit-Limit, RateLimit-Remaining, dan RateLimit-Reset, jadi Anda bisa memperlambat request sebelum mencapai batas. Jika batas terlampaui, API mengembalikan 429 dengan header Retry-After.
Paginasi
Daftar dipaginasi dengan cursor. Kirim limit untuk ukuran halaman (maks. 500) dan starting_after berisi id item terakhir di halaman sebelumnya. Lanjutkan sampai has_more bernilai false; next_cursor memberi Anda cursor untuk panggilan berikutnya. Limit di luar rentang tidak dipangkas diam-diam, melainkan menghasilkan error: pemangkasan diam-diam membuat klien mengira sudah mengambil semua data.
Perlindungan duplikat (Idempotency-Key)
Tambahkan header Idempotency-Key acak saat membuat pesanan. Jika koneksi terputus lalu Anda mengulang dengan key yang sama, pesanan kedua tidak akan dibuat: response pertama dikirim ulang dengan header Idempotent-Replay: true. Catatan disimpan selama 24 jam.
Mengirim key yang sama dengan body yang BERBEDA mengembalikan 409 idempotency_key_reuse. Ini hampir selalu berarti pembuatan key di sisi klien bermasalah. Request yang gagal tidak menghabiskan key: perbaiki masalahnya lalu ulangi dengan key yang sama.
Membuat pesanan
curl -X POST https://panelfollows.com/api/v3/orders \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8f14e45f-ea3a-4b1c-9c1e-2b0d5c6a7e91" \
-d '{
"service": 1234,
"link": "https://instagram.com/username",
"quantity": 1000
}'Skema field layanan
Setiap layanan menerbitkan field yang dibutuhkannya dalam array fields: nama, tipe, wajib atau tidak, batasnya, serta label dan deskripsi dalam bahasa Anda. Jika determines_quantity pada suatu field bernilai true, jumlah pesanan dihitung dari banyaknya baris di field tersebut.
// Servisin kendi alan şemasından formu OTOMATİK üretmek:
// hiçbir servis tipini koda gömmeniz gerekmez.
const res = await fetch("https://panelfollows.com/api/v3/services/1234", {
headers: { Authorization: "Bearer YOUR_API_KEY", "Accept-Language": "tr" },
});
const service = await res.json();
for (const field of service.fields) {
renderInput({
name: field.name,
label: field.label, // kullanıcının dilinde
hint: field.description, // kullanıcının dilinde
required: field.required,
type: field.type, // url | integer | string | text_lines
min: field.min,
max: field.max,
// true ise miktarı bu alanın satır sayısı belirler
countsLines: field.determines_quantity === true,
});
}Objek layanan
{
"object": "service",
"id": 1234,
"name": "Instagram Takipçi | Türk | 30 gün telafi",
"description": "Gerçek hesaplardan Türk takipçi. Başlangıç 0-1 saat.",
"type": "default",
"platform": "instagram",
"category": { "slug": "instagram-takipci", "name": "Instagram Takipçi" },
"pricing": {
"rate": "1.2340",
"currency": "USD",
"unit": "per_1000",
"unit_note": "Fiyat 1000 adet içindir."
},
"limits": { "min": 100, "max": 100000 },
"features": { "refill": true, "cancel": false, "dripfeed": true },
"average_time_seconds": 4320,
"fields": [
{
"name": "link",
"type": "url",
"required": true,
"label": "Bağlantı",
"description": "Gönderimin yapılacağı profilin herkese açık adresi."
},
{
"name": "quantity",
"type": "integer",
"required": true,
"label": "Miktar",
"description": "Kaç adet gönderileceği.",
"min": 100,
"max": 100000
}
],
"is_active": true,
"updated_at": "2026-08-20T09:15:00.000Z"
}Preview pesanan (dry run)
POST /orders/preview memvalidasi pesanan dan menghitung biayanya TANPA membuat pesanan. Tampilkan harganya kepada pelanggan Anda dan pastikan lebih dulu bahwa saldo Anda cukup. Tidak ada saldo yang dipotong dan tidak ada provider yang dihubungi.
Pesanan batch
POST /orders/batch menerima hingga 50 pesanan dalam satu panggilan. Item diproses sesuai urutan dan masing-masing melaporkan hasilnya sendiri: jika satu gagal, sisanya tetap dibuat, dan Anda bisa melihat persis mana yang gagal dan kenapa.
Menyertakan objek terkait
Kirim include=service pada endpoint pesanan agar objek layanan ikut disertakan di dalam response, sehingga Anda tidak perlu mengirim request kedua.
Kualitas layanan dan daftar layanan terbaik
Setiap jam, panel mengukur setiap layanan: bagaimana pesanan kami sendiri untuk layanan itu berakhir (selesai, dibatalkan, macet, ditolak), seberapa sering pelanggan meminta refill atau membuka tiket, berapa lama pengiriman benar-benar berlangsung, dan apakah sumbernya masih mencantumkan layanan tersebut. GET /services/top mengubah hasil pengukuran itu menjadi daftar pendek berperingkat, dan include=quality melampirkan laporan yang sama ke objek layanan mana pun.
Skor (0-100) adalah gabungan berbobot dari lima komponen: keandalan (42%, perkiraan tingkat keberhasilan), kepuasan (14%, dari tingkat komplain), kecepatan (28%, logaritmik terhadap waktu pengiriman efektif; 5 menit mendapat nilai penuh, 48 jam mendapat nol), skor dari mesin kesehatan layanan (8%), dan jumlah bukti (8%). Bonus kecil diberikan untuk garansi refill, sumber tepercaya, dan lamanya layanan ada di katalog. Layanan yang sedang ditinjau, ditandai oleh mesin kesehatan, atau berasal dari sumber yang masih dalam masa percobaan tetap diberi skor tetapi tidak pernah masuk peringkat.
Filter berdasarkan platform, slug kategori, atau shelf (jenis layanan seperti followers, likes, views..., sama di semua platform), urutkan berdasarkan score, speed, reliability, price, atau orders, dan gunakan group_by=category (atau platform) dengan per_group untuk mendapat satu daftar pendek per kategori dalam satu panggilan: persis yang dibutuhkan toko online untuk menandai layanan 'rekomendasi' di setiap kategori.
# Instagram takipçi kategorisinde en iyi 5 servis
curl "https://panelfollows.com/api/v3/services/top?category=instagram-followers&limit=5" -H "Authorization: Bearer YOUR_API_KEY"
# Her kategori için kanıtlı en iyi 3 servis (tek istek)
curl "https://panelfollows.com/api/v3/services/top?group_by=category&per_group=3&min_confidence=medium" -H "Authorization: Bearer YOUR_API_KEY"| score / grade | 0-100 dan A (85+), B (70+), C (55+), D. |
| confidence | none, low, medium, high: berapa banyak pesanan kami sendiri yang mendukung skor. |
| badges | proven (5+ pesanan, batas bawah keberhasilan 60%+), popular (20+ pesanan), fast (pengiriman dalam 1 jam), trusted_source, new (kurang dari 14 hari). |
| components | reliability, satisfaction, speed, health, evidence, masing-masing 0-1. |
| evidence.basis | service_orders (pesanan layanan itu sendiri), peer_services (dimulai dari layanan lain milik sumber yang sama), atau none. |
| evidence.delivery_source | measured (median kami sendiri dari 3+ pesanan selesai) atau claimed (waktu yang diklaim sumber, yang diberi penalti dan dibatasi). |
| measured_at | Kapan mesin kesehatan terakhir mengukur layanan; skor diperbarui setiap jam. |
Referensi endpoint
| Metode | Endpoint | Deskripsi |
|---|---|---|
| GET | /api/v3 | Discovery document: version, endpoints, limits and event types. |
| GET | /api/v3/openapi.json | OpenAPI 3.1 schema for this API. |
| GET | /api/v3/account | Account balance, currency and current rate-limit window. |
| PATCH | /api/v3/account | Set the low balance alert threshold that triggers account.low_balance. |
| GET | /api/v3/services | List services with filters and cursor pagination. |
| GET | /api/v3/services/top | Best services ranked by the quality score; optionally one shortlist per category or platform. |
| GET | /api/v3/services/{id} | Retrieve one service, including its order field schema. |
| GET | /api/v3/categories | List categories with platform, shelf and active service counts. |
| GET | /api/v3/platforms | List platform keys usable as the ?platform= filter, with counts. |
| POST | /api/v3/orders | Create an order. Supports the Idempotency-Key header. |
| POST | /api/v3/orders/preview | Validate an order and compute its charge without creating it. |
| POST | /api/v3/orders/batch | Create up to 50 orders in one call; each item reports its own result. |
| GET | /api/v3/orders | List your orders, newest first. |
| GET | /api/v3/orders/{id} | Retrieve one order. |
| POST | /api/v3/orders/{id}/cancel | Request cancellation. Only for services whose features.cancel is true. |
| POST | /api/v3/orders/{id}/refill | Request a refill for a completed order. |
| GET | /api/v3/refills | List your refill requests, newest first. |
| GET | /api/v3/refills/{id} | Retrieve one refill; refreshes its status from the provider. |
| GET | /api/v3/events | Read your event stream oldest-first; the polling alternative to webhooks. |
| GET | /api/v3/webhooks | List your webhook endpoints. |
| POST | /api/v3/webhooks | Register a webhook endpoint. The signing secret is returned once. |
| GET | /api/v3/webhooks/{id} | Retrieve one webhook endpoint. |
| PATCH | /api/v3/webhooks/{id} | Update a webhook endpoint's url, events, description or active state. |
| DELETE | /api/v3/webhooks/{id} | Delete a webhook endpoint and its delivery log. |
| POST | /api/v3/webhooks/{id}/test | Send a test event to this endpoint, ignoring its event filter. |
| POST | /api/v3/webhooks/{id}/rotate_secret | Generate a new signing secret. The old one stops working immediately. |
| GET | /api/v3/webhooks/{id}/deliveries | Delivery log for one endpoint: attempts, response codes and errors. |
Skema OpenAPI
Definisi setiap endpoint dalam format yang bisa dibaca mesin. Masukkan file ini ke generator klien (openapi-generator, Kiota) atau ke Postman, dan Anda langsung mendapat klien siap pakai dalam bahasa pemrograman Anda sendiri.
https://panelfollows.com/api/v3/openapi.jsonPertanyaan umum
Apakah API key legacy saya berfungsi di v3?
Ya. Anda tidak perlu API key baru untuk mencobanya. Meski begitu, untuk production sebaiknya pindah ke API key v3: API key v3 bisa dicabut dan tidak disimpan dalam bentuk teks biasa.
Kenapa harga dikirim sebagai string?
Angka floating point kehilangan pecahan pada nominal desimal. Dengan mengirim string dan mem-parse-nya ke tipe desimal di sisi Anda, seluruh jenis selisih pembulatan itu hilang.
Kenapa field unit penting?
Sebagian besar layanan dihargai per 1.000 unit (per_1000), tetapi layanan paket dijual sebagai satu item (per_order) dengan tarif yang mencakup seluruh paket. Integrasi yang mengabaikan perbedaan ini menghitung harga paket meleset 1.000 kali lipat.
Pesanan saya sudah dibuat tetapi tetap pending. Apa yang terjadi?
Pengiriman pesanan ke provider mungkin tertunda. Saldo Anda ditahan dan pesanan tidak hilang; tim kami mengirimkannya ulang secara otomatis. Field processing_delayed menandai kondisi ini.
Apakah semua layanan mendukung pembatalan dan refill?
Tidak. Periksa features.cancel dan features.refill pada objek layanan. Memanggil endpoint untuk layanan yang tidak mendukungnya akan mengembalikan 400.
Bisakah saya memakai kedua API sekaligus?
Bisa. Akun sama, saldo sama, pesanan sama. Pesanan yang dibuat lewat v2 bisa dibaca lewat v3.
API reseller klasik yang menjadi standar di seluruh industri. Inilah format yang diharapkan software panel siap pakai.
Endpoint
POST https://panelfollows.com/api/v2
POST https://panelfollows.com/api/v2/trAutentikasi
Setiap request membawa parameter key. Jaga kerahasiaan API key Anda dan segera buat ulang jika bocor.
Format request dan response
Request dikirim dengan POST sebagai form (application/x-www-form-urlencoded) dan response berupa JSON. Kegagalan juga mengembalikan HTTP 200, dengan { "error": "..." } di dalam body.
Juga didukung meski belum pernah didokumentasikan: Anda bisa memanggilnya dengan GET, dan body boleh dikirim sebagai application/json.
Batas request (rate limit)
240 request per menit per API key, ditambah 300 per menit per IP. Request yang melebihi batas ditolak dengan 429.
Action dan parameter
| action | Parameter | Deskripsi |
|---|---|---|
| services | key, action | Menampilkan semua layanan aktif (id, nama, kategori, tarif, min/maks, refill, pembatalan, drip-feed). |
| add | key, action, service, link, quantity[, runs, interval, comments, username, posts, min, max] | Membuat pesanan. service adalah id layanan di katalog. Tambahkan runs dan interval untuk drip-feed, serta field yang sesuai untuk tipe khusus. |
| status | key, action, order | orders | Status pesanan. Gunakan order untuk satu pesanan, atau daftar orders yang dipisahkan koma untuk banyak pesanan. |
| balance | key, action | Saldo akun dan mata uangnya. |
| refill | key, action, order | orders | Membuat permintaan refill yang langsung dikirim ke provider. |
| refill_status | key, action, refill | refills | Mengecek status refill. |
| cancel | key, action, orders | Membatalkan pesanan. Hanya berlaku untuk layanan yang provider-nya mendukung pembatalan. |
Contoh
curl -X POST https://panelfollows.com/api/v2 \
-d "key=YOUR_API_KEY" \
-d "action=add" \
-d "service=1234" \
-d "link=https://instagram.com/username" \
-d "quantity=1000"
# Yanıt: { "order": 23501 }Response dalam bahasa Turki
Tambahkan /tr di akhir URL untuk menerima nama layanan, kategori, status pesanan, dan pesan error dalam bahasa Turki. Parameter, action, dan bentuk response tetap sama, dan API key Anda berfungsi di kedua URL. Field teknis (type, refill_status, currency) tetap dalam bahasa Inggris demi kompatibilitas standar.
Pindah ke v3
Migrasi bersifat opsional. Jika Anda pindah, sebagian besar logika bisnis Anda tetap terpakai karena nama parameternya tidak berubah; yang berbeda hanya cara request dikirim dan cara membaca error.
- 1Pindahkan API key dari field key di body ke header Authorization: Bearer.
- 2Panggil path resource, bukan action=... (POST /orders, bukan add).
- 3Deteksi kegagalan dari status HTTP dan error.code, bukan dari "ada field error atau tidak".
- 4Bandingkan status pesanan dengan nilai mesin, bukan teks tampilannya.
- 5Tambahkan Idempotency-Key saat membuat pesanan.
- 6Ganti polling status dengan webhook.
API key memberi akses penuh ke akun Anda. Jangan dibagikan, jangan disematkan di kode sisi klien, dan jangan pernah meng-commit-nya ke repositori publik.
API key v3
Buat API key sebanyak yang Anda perlukan, beri label pada masing-masing, dan cabut satu per satu. Di sisi kami hanya disimpan digest kriptografis dari API key tersebut.
Buat akun gratisAPI key legacy
Satu-satunya API key yang dipakai API reseller klasik (v2). API key ini juga berfungsi di v3. Jika dibuat ulang, nilai lamanya langsung tidak berlaku.
Keamanan
- Simpan API key di environment variable, jangan pernah di source code.
- Jangan taruh API key di kode yang berjalan di browser; teruskan panggilan lewat server Anda sendiri.
- Buat API key terpisah untuk setiap sistem, supaya mencabut satu API key tidak memengaruhi yang lain.
- Jika Anda curiga ada kebocoran, pasang API key baru lebih dulu, baru cabut yang lama.
Saat status pesanan berubah, kami mengirim notifikasi bertanda tangan ke server Anda, jadi Anda tidak perlu lagi mengecek status lewat polling.
Kenapa webhook?
Polling itu lambat sekaligus boros: menanyakan ribuan pesanan setiap menit menghabiskan jatah rate limit Anda, dan Anda tetap baru tahu perubahannya beberapa menit kemudian. Dengan webhook, perubahan sampai ke Anda saat itu juga.
Pengaturan
- 1Siapkan URL https publik (alamat jaringan lokal dan privat ditolak).
- 2Tambahkan URL di bawah dan simpan signing secret yang hanya ditampilkan sekali.
- 3Verifikasi tanda tangan di sisi Anda dan balas dengan kode 2xx.
- 4Gunakan tombol tes untuk memastikan seluruh alur berjalan dari awal sampai akhir.
Yang kami kirim
POST /hooks/pf HTTP/1.1
Content-Type: application/json
Webhook-Id: evt_7f910fba7cd042ef9d9069ba5c074fa0
Webhook-Timestamp: 1787261223
Webhook-Signature: t=1787261223,v1=9c1e2b0d5c6a7e91...
{
"object": "event",
"id": "evt_7f910fba7cd042ef9d9069ba5c074fa0",
"type": "order.completed",
"created_at": "2026-08-21T00:27:03.531Z",
"data": {
"previous_status": "in_progress",
"order": {
"object": "order",
"id": 23501,
"status": "completed",
"status_label": "Tamamlandı",
"service": 1234,
"quantity": 1000,
"start_count": 4210,
"remains": 0,
"charge": "1.2340",
"currency": "USD"
}
}
}Memverifikasi tanda tangan
Setiap request membawa header Webhook-Signature: t adalah timestamp dan v1 adalah tanda tangannya. Tanda tangan itu adalah HMAC-SHA256 dari string "<timestamp>.<raw body>" dengan secret Anda.
- 1Ambil nilai t dan v1 dari header.
- 2Pastikan t tidak lebih dari 5 menit yang lalu, untuk mencegah serangan replay.
- 3Hitung HMAC-SHA256 dari "<t>.<raw body>" dengan secret Anda.
- 4Bandingkan hasilnya dengan v1 secara constant time, dan tolak request jika tidak cocok.
Contoh verifikasi
import crypto from "node:crypto";
import express from "express";
const app = express();
// ÖNEMLİ: imza HAM gövde üzerinden hesaplanır. JSON'a çevirip yeniden
// dizeye dönüştürürseniz boşluklar değişir ve imza tutmaz.
app.post("/hooks/pf", express.raw({ type: "application/json" }), (req, res) => {
const raw = req.body.toString("utf8");
const header = req.get("Webhook-Signature") ?? "";
const m = /t=(\d+),v1=([0-9a-f]+)/.exec(header);
if (!m) return res.sendStatus(400);
const [, timestamp, signature] = m;
// Tekrar saldırısına karşı: 5 dakikadan eski damgayı reddet.
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.sendStatus(400);
const expected = crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(`${timestamp}.${raw}`, "utf8")
.digest("hex");
const ok =
expected.length === signature.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
if (!ok) return res.sendStatus(401);
const event = JSON.parse(raw);
// 2xx dönmezseniz gönderim artan aralıklarla tekrar denenir.
res.sendStatus(200);
if (event.type === "order.completed") {
// ... siparişi kendi sisteminizde tamamlandı olarak işaretleyin
}
});Pengiriman ulang
Percobaan pertama dilakukan saat event terjadi. Jika response bukan 2xx atau koneksi gagal, pengiriman diulang setelah 1 menit, 5 menit, 30 menit, 2 jam, dan 6 jam. Setelah 6 percobaan, pengiriman ditandai gagal dan muncul di log pengiriman.
Jenis event
Berlangganan jenis event yang Anda perlukan, atau terima semuanya. Satu perubahan status menghasilkan tepat satu event, dengan jenis yang paling sesuai dengan status barunya.
| order.created | Pesanan dibuat. |
| order.processing | Provider mulai memproses pesanan. |
| order.completed | Pesanan selesai. |
| order.partial | Pesanan terkirim sebagian dan sisanya dikembalikan ke saldo. |
| order.canceled | Pesanan dibatalkan atau dananya dikembalikan. |
| order.updated | Status berubah dengan cara lain. |
| refill.created | Permintaan refill dibuat. |
| refill.updated | Status refill berubah. |
| account.low_balance | Saldo Anda turun di bawah ambang yang Anda atur lewat PATCH /account. Dipicu saat saldo turun melewati ambang itu, bukan pada setiap pesanan, dan aktif kembali setelah saldo naik lagi di atasnya. |
Jika Anda tidak bisa menyediakan endpoint webhook
Event yang sama bisa dibaca dengan cursor dari GET /api/v3/events. Gunakan cara ini selama pengembangan di lokal, saat tidak punya IP statis, atau saat berada di balik firewall.
// Webhook kuramıyorsanız (yerelde geliştirme, sabit IP yok) aynı bilgiyi
// imleçle çekebilirsiniz. İmleci kendi tarafınızda saklayın.
let cursor = loadCursor(); // en son işlediğiniz olayın "cursor" değeri
const url = new URL("https://panelfollows.com/api/v3/events");
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("starting_after", String(cursor));
const res = await fetch(url, { headers: { Authorization: "Bearer YOUR_API_KEY" } });
const { data, has_more } = await res.json();
for (const event of data) {
handle(event); // sizin işleyiciniz
cursor = event.cursor; // imleci ilerlet
}
saveCursor(cursor);Error (48)
| Kode | Status | Deskripsi |
|---|---|---|
| missing_api_key | 401 | Tidak ada API key yang dikirim. Kirim sebagai 'Authorization: Bearer <key>'. |
| invalid_api_key | 401 | API key yang Anda kirim tidak valid. |
| revoked_api_key | 401 | API key ini sudah dicabut dan tidak bisa dipakai lagi. |
| account_banned | 403 | Akun ini diblokir. |
| account_suspended | 403 | Akun ini ditangguhkan. |
| insufficient_scope | 403 | API key ini tidak punya izin untuk endpoint ini. |
| invalid_json | 400 | Body request bukan JSON yang valid. |
| unsupported_content_type | 415 | Content-Type tidak didukung. Gunakan application/json atau application/x-www-form-urlencoded. |
| method_not_allowed | 405 | Metode HTTP ini tidak diizinkan pada endpoint ini. |
| payload_too_large | 413 | Body request terlalu besar. |
| missing_parameter | 400 | Ada parameter wajib yang tidak dikirim. |
| invalid_parameter | 400 | Nilai salah satu parameter tidak valid. |
| invalid_link | 400 | Link kosong atau bukan URL http(s) yang valid. |
| invalid_quantity | 400 | Jumlah bukan bilangan bulat positif yang valid. |
| quantity_out_of_range | 400 | Jumlah di luar batas yang diizinkan layanan ini. |
| invalid_comments | 400 | Field komentar kosong atau barisnya terlalu banyak. |
| invalid_username | 400 | Nama pengguna tidak valid untuk layanan ini. |
| invalid_subscription | 400 | Parameter langganan tidak valid. |
| invalid_runs | 400 | Nilai 'runs' tidak valid untuk drip-feed. |
| invalid_interval | 400 | Nilai 'interval' tidak valid untuk drip-feed. |
| dripfeed_not_supported | 400 | Layanan ini tidak mendukung drip-feed. |
| missing_required_field | 400 | Field yang wajib untuk jenis layanan ini tidak ada atau tidak valid. |
| service_inactive | 400 | Layanan ini sedang tidak bisa dipesan. |
| invalid_cursor | 400 | Cursor paginasi tidak valid. |
| invalid_limit | 400 | Parameter 'limit' di luar rentang yang diizinkan. |
| invalid_webhook_url | 400 | URL webhook harus berupa alamat https:// publik. |
| invalid_events | 400 | Satu atau beberapa jenis event yang diminta tidak dikenal. |
| batch_too_large | 400 | Terlalu banyak item dalam satu request batch. |
| cancel_not_supported | 400 | Layanan ini tidak mendukung pembatalan. |
| refill_not_supported | 400 | Layanan ini tidak menyediakan refill. |
| unknown_endpoint | 404 | Endpoint tidak dikenal. Lihat referensi API untuk daftar route yang tersedia. |
| service_not_found | 404 | Tidak ada layanan dengan id ini. |
| order_not_found | 404 | Tidak ada pesanan dengan id ini di akun Anda. |
| refill_not_found | 404 | Tidak ada refill dengan id ini di akun Anda. |
| webhook_not_found | 404 | Tidak ada endpoint webhook dengan id ini di akun Anda. |
| order_not_cancelable | 409 | Pesanan ini tidak bisa dibatalkan lagi karena statusnya saat ini. |
| cancel_rejected | 409 | Provider menolak permintaan pembatalan. |
| order_not_completed | 409 | Refill hanya bisa diminta untuk pesanan yang sudah selesai. |
| duplicate_link | 409 | Sudah ada pesanan aktif untuk link ini. Tunggu sampai pesanan itu selesai. |
| idempotency_key_reuse | 409 | Idempotency-Key ini sudah dipakai dengan body request yang berbeda. |
| idempotency_in_progress | 409 | Request dengan Idempotency-Key ini masih diproses. Coba lagi dalam beberapa saat. |
| webhook_limit_reached | 409 | Anda sudah mencapai jumlah maksimum endpoint webhook. |
| insufficient_balance | 402 | Saldo tidak cukup untuk pesanan ini. |
| rate_limit_exceeded | 429 | Batas request terlampaui. Lihat header response Retry-After. |
| provider_error | 502 | Provider mengembalikan error. Coba lagi. |
| refill_failed | 502 | Permintaan refill ditolak oleh provider. |
| service_temporarily_unavailable | 503 | Layanan ini sedang tidak tersedia untuk sementara. Coba lagi nanti. |
| internal_error | 500 | Terjadi kesalahan tak terduga di sistem kami. |