Özet: /webhook rotanıza herhangi bir JSON middleware'den önce express.raw({ type: 'application/json' }) bağlayın; her Stripe imza doğrulamasının başarılı ya da başarısız olmasını belirleyen tek karar budur. Abonenin telefonundaki başındaki '+' işaretini kaldırın ve @s.whatsapp.net ekleyin. checkout.session.completed olayında /groups/{id}/participants'a POST, customer.subscription.deleted olayında DELETE gönderin. Google Sheets 50 üyeye kadar işe yarar. SQLite geri kalanını karşılar. Tüm kod bu rehberde yer almaktadır.
2026'da Ücretli WhatsApp Gruplarını Kim Kullanıyor (ve Manuel Yönetim Neden Çöküyor)
Fitness koçları, kripto sinyal sağlayıcılar, emlak fırsatı arayıcılar ve sınav hazırlık öğretmenleri ücretli WhatsApp gruplarını geniş ölçekte yönetiyor; neredeyse tamamı üyeliği 50 aboneyi geçince güvenilir biçimde bozulan bir tabloda takip ediyor.
Pazar dört dikey segmentte yerleşik durumda: fitness (9,99--140$/ay), kripto sinyalleri (safetrading.today'de 49--149$/ay), Nijerya ve Güney Afrika'da sınav hazırlık öğretmenleri (₦1.500--R149/ay) ve İngiliz perakendeci Love Luxe gibi lüks perakende grupları; burada davet bağlantısı sızıntıları da aynı derecede aktif. dhryvyad/wahooks (MIT, 2026) gibi açık kaynak projeler, geliştiricilerin ticari SaaS'ın ötesinde kendi kendine yönetilen çözümlere olan ilgisini doğrular.
Operasyonel maliyetler sayısal olarak belgelenmiş durumda. Tam olarak bu sorun için aylık 29--299$ talep eden SaaS CommuniPass, yaklaşık 50 üyeye ulaşıldığında tutarlı hatalar yaşandığını belgeliyor; ödeme tarihlerini WhatsApp görünen adlarıyla karşılaştırmak bu noktada insan hata toleransını aşıyor. Zaman maliyeti saatte 50$ oranında haftada yaklaşık 8 saat, yani yılda 20.800 dolar.
Gelir kaybı sorunu bundan ayrıdır ve pratikte daha ağırdır. Gruplar, ödemeleri sona eren üyeler grupta kalmaya devam ettiği için abonelik gelirinin %15--20'sini kaybeder. Bu sorunun süregelmesinin nedeni sosyaldir: WhatsApp, birisi uygulama arayüzü üzerinden kaldırıldığında tüm gruba görünür bir bildirim gönderir. Bir operatörün ifadesiyle: "Üyeler abonelikleri sona erdikten sonra grupta kalıyor çünkü onları manuel olarak atmak can sıkıcı." Bu rehberde ele alınan API düzeyindeki DELETE çağrısı, diğer üyelere görünür herhangi bir bildirim göndermeden üyeleri kaldırır.
Zapier + Davet Bağlantısı Yaklaşımı İlk Paylaşımda Neden Başarısız Olur
İlk aklınıza Zapier ya da Make gelebilir: Stripe ödeme olayı → grup davet bağlantısını çek → e-posta veya SMS ile gönder. Bir abone bağlantıyı paylaşana kadar bu çalışır.
NothingApps tam olarak bu başarısızlık modunu belgeledi: "VIP grubunuzda bir yer satıyorsunuz. Bir saat içinde alıcı bağlantıyı bir genel forumda paylaştığı için beş hayalet üye katılmış oluyor." Her aboneye gönderilen statik bir davet bağlantısı, kapatma mekanizması olmayan kümülatif bir sızıntı yaratır. Zapier akışı ayrıca abonelikler sona erdiğinde üyeleri kaldıracak bir yol sunmaz; ödeme başarısına tepki verir ancak grup üyeliği üzerinde DELETE yeteneğine sahip değildir ve abonelik sonlandırması için tetikleyici yoktur.
Bu rehberin ele aldığı yaklaşım, paylaşılabilir bir bağlantı üzerinden değil, telefon numarasına göre tek tek üye ekler ve kaldırır. Davet bağlantısı rotasyonu yalnızca bir abonenin gizlilik ayarlarının doğrudan API eklemeyi engellediği uç durum için yedek olarak kullanılabilir kalmaktadır; bu durum aşağıdaki davet bağlantısı bölümünde ele alınmaktadır.
Tam Sistem: Stripe Checkout → Webhook → Whapi.Cloud
Beş bileşen, iki Stripe olayı, bir Whapi.Cloud token'ı. Üyelik yaşam döngüsünün tamamı bir veritabanı modülü ve iki API rotasıyla tek bir Express dosyasına sığar.
Çalışma akışı:
-
Checkout oturumu oluşturma: Abone Stripe'ın barındırdığı ödeme sayfasını tamamlar. WhatsApp telefon numarası, ödeme onayında değil oturum oluşturulurken
metadata.whatsapp_phone'a kaydedilir. -
checkout.session.completed: Stripe bu olayı başarılı ödemeden saniyeler sonra iletir. İşleyiciniz meta verilerden telefonu okur, WhatsApp JID'ye dönüştürür vePOST /groups/{id}/participants'ı çağırır. Abone grupta. -
Aktif üyelik: Veritabanınız telefonu, Stripe abonelik kimliğini ve grup kimliğini kaydeder. Günlük bir cron işi webhook'un kaçırdığı kayıtları kontrol eder.
-
customer.subscription.deleted: İptal sırasında veya Stripe ödeme denemelerini tükettiğinde tetiklenir. İşleyiciniz üyenin JID'iyleDELETE /groups/{id}/participants'ı çağırır. -
Sessiz kaldırma: DELETE çağrısı, diğer üyelere WhatsApp grubu bildirimi gönderilmeden üyeyi kaldırır. Sosyal sürtüşme sorunu ortadan kalkar.
customer.subscription.deleted, süre sonu kaynaklı kaldırmayı garanti eden tek Stripe olayıdır. Yaygın bir kablolama hatası, kaldırma işlemini invoice.payment_failed'e yönlendirmektir. Bu olay, Stripe aboneliği kesin olarak sonlandırdığında değil, her başarısız yeniden deneme girişiminde (varsayılan olarak 4'e kadar) tetiklenir. Abonelik yeniden deneme sonrasında kurtarılabilir. customer.subscription.deleted'ı kullanın.
Stripe Checkout'ta Abonenin WhatsApp Telefonu Nasıl Yakalanır
Abonenin WhatsApp telefon numarasını oturum oluşturulurken alın. Ayrı bir müşteri profili arama sistemi oluşturmadan bunu sonradan Stripe'tan güvenilir biçimde alamazsınız.
Stripe yönlendirmesinden önce frontend'inizde bir telefon giriş adımı ekleyin. Stripe'ın metadata alanı üzerinden Checkout Session oluştururken değeri arka uca aktarın:
// server.js -- create a Stripe Checkout Session with phone metadata
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/create-checkout', async (req, res) => {
const { phone } = req.body; // e.g. "+12025551234" from your pre-checkout form
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [{ price: process.env.STRIPE_PRICE_ID, quantity: 1 }],
// Store WhatsApp phone here -- webhook reads it from event.data.object.metadata.whatsapp_phone
metadata: { whatsapp_phone: phone },
success_url: `${process.env.BASE_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.BASE_URL}/cancel`,
});
res.json({ url: session.url });
});
checkout.session.completed tetiklendiğinde oturum meta verilerinde whatsapp_phone yoksa webhook işleyiciniz üyeyi otomatik olarak ekleyemez. Bu alanı checkout formunuzda doğrulayın. 10 basamaktan kısa gönderimleri reddedin ve bir sonraki adımda E.164-JID dönüşümünün düzgün çalışması için uluslararası ön ek gerektirin.
Tamamlanmadaki oturum nesnesi aynı zamanda subscription kimliğini de içerir. Bunu veritabanınızda telefon numarasıyla birlikte kaydedin; customer.subscription.deleted daha sonra tetiklendiğinde arama anahtarı budur. Bu çifti olmadan kaldırma olayı hangi telefon numarasını kaldıracağını belirleyemez.
Stripe Webhook İmzaları Neden Başarısız Olur — ve Raw-Body ile Çözümü
Webhook işleyicinizden önce express.json() çalışırsa her Stripe imza doğrulaması sessizce başarısız olur. Düzeltme doğru konuma yerleştirilmiş tek bir satırdır; ancak ilk kez teşhis etmek genellikle saatler alır.
express.json()'ı genel olarak kaydedip ardından bir /webhook rotası ekleyen ekipler, doğru gizli anahtarla bile stripe.webhooks.constructEvent()'in her çağrıda "No signatures found matching the expected signature for payload" fırlattığını görür. Hata hiçbir ipucu vermez. Nedeni: express.json(), istek gövdesini zaten yeniden serileştirmiştir; bu da Stripe'ın orijinal ham baytlar üzerinde hesapladığı HMAC-SHA256 imzasını geçersiz kılacak ölçüde bayt temsilini değiştirir.
Önce ham gövde kuralı: express.json()'ı genel olarak kaydetmeden önce /webhook rotasına express.raw({ type: 'application/json' }) bağlayın. Express'te rotaya özgü middleware önce çalışır; böylece webhook rotası, global parser dokunmadan Buffer alır.
// server.js -- middleware registration order is non-negotiable
// without express.raw() here, stripe.webhooks.constructEvent throws 400
// even when STRIPE_WEBHOOK_SECRET is correct and matches your dashboard
app.use('/webhook', express.raw({ type: 'application/json' }), webhookHandler);
// All other routes get parsed JSON normally
app.use(express.json());
app.use('/create-checkout', checkoutRouter);
Rota sırası doğru olduğunda webhook işleyicisi imzayı doğrular ve olayları uygun iş mantığına yönlendirir:
async function webhookHandler(req, res) {
const sig = req.headers['stripe-signature'];
let event;
try {
// req.body is a raw Buffer here -- not a parsed object
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
console.error('Webhook signature verification failed:', err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Idempotency gate: Stripe delivers each event at least once.
// A duplicate checkout.session.completed adds the member twice; a duplicate
// customer.subscription.deleted removes a still-active subscriber.
if (await db.hasProcessedEvent(event.id)) {
return res.status(200).send('Already processed');
}
switch (event.type) {
case 'checkout.session.completed':
await handleCheckoutCompleted(event.data.object);
break;
case 'customer.subscription.deleted':
await handleSubscriptionDeleted(event.data.object);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
await db.markEventProcessed(event.id);
res.status(200).send('OK');
}
Idempotency kontrolü, başarılı işlemden sonra her Stripe olay kimliğini kaydeden bir processed_events tablosuna dayanır. Bu tablonun şeması aşağıdaki veritabanı bölümünde yer almaktadır.
Telefon Numarası Normalleştirme: Hataların %90'ını Önleyen İki İşlem
'+' kaldırın, '@s.whatsapp.net' ekleyin. İki string işlemi, sıfır belirsizlik. Stripe + Whapi.Cloud entegrasyonlarındaki diğer tüm hatalar bu adımlardan birinin atlanmasından kaynaklanır.
WhatsApp kişileri telefon numarasıyla değil JID (Jabber ID) ile adresler. Format şöyledir: {country_code}{national_number}@s.whatsapp.net. Stripe numaraları başında '+' bulunan E.164 formatında saklar. Dönüşüm deterministiktir:
// Converts E.164 phone → WhatsApp JID
// Input: "+12025551234" → Output: "[email protected]"
// Input: "12025551234" → Output: "[email protected]" (no + present)
function toWhatsAppJID(e164Phone) {
const digits = String(e164Phone).replace(/^\+/, '');
return `${digits}@s.whatsapp.net`;
}
// Optional: validate before using
function isValidJID(jid) {
return /^\d{7,15}@s\.whatsapp\.net$/.test(jid);
}
Ekipler, her addGroupParticipant çağrısının genel bir hatayla başarısız olma nedenini saatlerce hata ayıkladıktan sonra JID'in temizlenmemiş bir giriş alanından gelen '+' veya boşluk içerdiğini keşfeder. Bu, yeni Stripe + Whapi.Cloud entegrasyonlarındaki en sık görülen sessiz hatadır. Her API çağrısından önce isValidJID() çağırın ve false döndürürse açık bir hata kaydedin.
Checkout formunda ele alınacak iki uç durum: (1) boşluk veya tire içeren numaralar — '+' kaldırmadan önce rakam olmayan tüm karakterleri temizleyin; (2) ülke kodu olmayan numaralar — 10 basamaktan kısa girişleri reddedin ve uluslararası format isteyin.
Ödeme Gelince Ekle, Abonelik Bitince Çıkar
İki Stripe olay işleyicisi, Whapi.Cloud'a dört HTTP çağrısı. Ödeme sırasında ekleme yolu checkout'ta çalışır; süre dolduğunda kaldırma yolu abonelik sona erdiğinde devreye girer.
Checkout işleyicisi telefonu oturum meta verilerinden okur, üyeyi ekler ve son kullanma tarihini takip etmek için kaydı saklar:
async function handleCheckoutCompleted(session) {
const phone = session.metadata?.whatsapp_phone;
if (!phone) {
console.error('No whatsapp_phone in session metadata -- member cannot be added automatically');
return;
}
const groupId = process.env.WHAPI_GROUP_ID;
await addGroupMember(groupId, phone);
await db.saveMember({
phone,
stripeSubscriptionId: session.subscription,
stripeCustomerId: session.customer,
groupId,
});
console.log(`Member added: ${phone} (subscription: ${session.subscription})`);
}
async function handleSubscriptionDeleted(subscription) {
const member = await db.getMemberBySubscriptionId(subscription.id);
if (!member) {
// Already removed manually, or was never successfully added
console.warn(`No member found for subscription ${subscription.id}`);
return;
}
await removeGroupMember(member.groupId, member.phone);
await db.removeMember(subscription.id);
console.log(`Member removed: ${member.phone} (subscription: ${subscription.id})`);
}
Whapi.Cloud API çağrıları, Groups API'ye yapılan doğrudan HTTP istekleridir. Ekleme çağrısı abonenin JID'ini katılımcı olarak gönderir:
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// POST https://gate.whapi.cloud/groups/{GroupID}/participants
async function addGroupMember(groupId, phone) {
const jid = toWhatsAppJID(phone);
const response = await fetch(
`https://gate.whapi.cloud/groups/${groupId}/participants`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.WHAPI_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ participants: [jid] }),
}
);
const data = await response.json();
if (!response.ok) {
// 403 means the subscriber's privacy settings block direct group addition
// Fall back to sending them a rotated invite link via DM
if (response.status === 403) {
console.warn(`Direct add blocked for ${phone}, sending invite link`);
return sendInviteLink(groupId, phone);
}
throw new Error(`addGroupMember failed (${response.status}): ${JSON.stringify(data)}`);
}
// Sleep >250ms between consecutive adds -- prevents IQErrorRateOverlimit
// which fires after ~4 rapid additions and requires a channel-level retry
await sleep(300);
return data;
}
// DELETE https://gate.whapi.cloud/groups/{GroupID}/participants
async function removeGroupMember(groupId, phone) {
const jid = toWhatsAppJID(phone);
const response = await fetch(
`https://gate.whapi.cloud/groups/${groupId}/participants`,
{
method: 'DELETE',
headers: {
'Authorization': `Bearer ${process.env.WHAPI_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ participants: [jid] }),
}
);
// This DELETE is silent: no WhatsApp group notification is sent to other members
return response.json();
}
removeGroupMember'daki DELETE çağrısı grupta hiçbir bildirim oluşturmaz. WhatsApp'taki "X yönetici tarafından kaldırıldı" mesajı yalnızca bir insan yöneticisi WhatsApp uygulamasını kullandığında görünür. DELETE /groups/{id}/participants aracılığıyla API kaynaklı kaldırma, diğer üyelere görünmez. Abone katılımcı listesinden sessizce kaybolur. Birini manuel olarak atmakla ilgili tüm sosyal rahatsızlık da onunla birlikte ortadan kalkar.
Hız Sınırları, Idempotency ve 403 Yedek Çözümü
Her production deployment'ın karşılaşacağı üç hata modu; her biri için deterministik bir çözüm. İlk canlı abone ödemesinden önce üçünü de koda ekleyin.
IQErrorRateOverlimit, addParticipants'ı çok hızlı çağırdığınızda tetiklenen WhatsApp sunucu tarafı hatasıdır. Bir geliştirici bu durumu GitHub issue'sunda şu şekilde belgeledi: "group.addParticipants() çağrılırken bilinmeyen hata (dahili olarak IQErrorRateOverlimit) — genellikle varsayılan sleep değerleriyle 4 üye başarıyla eklendikten sonra ortaya çıkar." Yukarıdaki addGroupMember()'daki çözüm, her bireysel eklemeden sonra await sleep(300)'dur. 50'den fazla mevcut aboneyi aynı anda eklediğiniz taşıma toplu işlemleri için sleep'i 500ms'ye çıkarın ve toplu işlemler arasında 3 saniyelik duraklamayla 10'luk gruplar halinde işleyin.
Aşağıdaki veritabanı modülü hem idempotency kontrolünü hem de üye kaydı depolamayı uygular. Asenkron akışı basitleştiren eşzamanlı SQLite erişimi için better-sqlite3 kullanır:
// db.js -- SQLite member store with idempotency
const Database = require('better-sqlite3');
const db = new Database('members.db');
db.exec(`
CREATE TABLE IF NOT EXISTS members (
id INTEGER PRIMARY KEY AUTOINCREMENT,
phone TEXT NOT NULL,
stripe_subscription_id TEXT UNIQUE NOT NULL,
stripe_customer_id TEXT NOT NULL,
group_id TEXT NOT NULL,
created_at INTEGER DEFAULT (unixepoch())
);
CREATE TABLE IF NOT EXISTS processed_events (
event_id TEXT PRIMARY KEY,
processed_at INTEGER DEFAULT (unixepoch())
);
`);
module.exports = {
hasProcessedEvent: (id) =>
!!db.prepare('SELECT 1 FROM processed_events WHERE event_id = ?').get(id),
markEventProcessed: (id) =>
db.prepare('INSERT OR IGNORE INTO processed_events (event_id) VALUES (?)').run(id),
saveMember: ({ phone, stripeSubscriptionId, stripeCustomerId, groupId }) =>
db.prepare(`
INSERT OR REPLACE INTO members (phone, stripe_subscription_id, stripe_customer_id, group_id)
VALUES (?, ?, ?, ?)
`).run(phone, stripeSubscriptionId, stripeCustomerId, groupId),
getMemberBySubscriptionId: (sid) =>
db.prepare('SELECT * FROM members WHERE stripe_subscription_id = ?').get(sid),
removeMember: (sid) =>
db.prepare('DELETE FROM members WHERE stripe_subscription_id = ?').run(sid),
};
addGroupParticipant'tan gelen 403, abonenin WhatsApp gizlilik ayarlarının sunucu tarafından grup eklemelerini engellediği anlamına gelir. Tek çözüm yolu: o aboneye doğrudan döndürülmüş bir davet bağlantısı göndermek. Bu, geçersiz kılamayacağınız cihaz düzeyinde bir ayardır. Hata şekli için gruba yeni üye ekleme API referansına bakın. Yedek çözüm, bir sonraki bölümdeki sendInviteLink() ile ele alınır.
Davet Bağlantısı Rotasyonu: Ücretsiz Üye Girişini Kapatmak
Birine bir davet bağlantısı her gönderdiğinizde hemen iptal edin ve yeni bir tane oluşturun. Döndürülmemiş bir bağlantı, bir saat içinde ücret ödemeden üyelerle grubunuzu doldurabilir.
Mekanizma: DELETE /groups/{id}/invite mevcut davet kodunu anında geçersiz kılar. GET /groups/{id}/invite yeni bir kod oluşturur ve döndürür. Yeni bağlantı yalnızca doğrudan WhatsApp DM aracılığıyla o aboneye gider. DELETE'ten önce var olan tüm kodlar (önceki bir testten, geçmiş bir aboneden veya sızdırılmış bir ekran görüntüsünden) hemen çalışmayı durdurur.
// DELETE https://gate.whapi.cloud/groups/{GroupID}/invite (revoke current code)
// GET https://gate.whapi.cloud/groups/{GroupID}/invite (get new code)
// POST https://gate.whapi.cloud/messages/text (send link via DM)
async function sendInviteLink(groupId, phone) {
// Step 1: Revoke -- any previously distributed link stops working now
await fetch(`https://gate.whapi.cloud/groups/${groupId}/invite`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${process.env.WHAPI_TOKEN}` },
});
// Step 2: Get fresh invite code
const res = await fetch(`https://gate.whapi.cloud/groups/${groupId}/invite`, {
method: 'GET',
headers: { 'Authorization': `Bearer ${process.env.WHAPI_TOKEN}` },
});
const { invite_code } = await res.json();
const inviteLink = `https://chat.whatsapp.com/${invite_code}`;
// Step 3: Send the unique link to this subscriber only via DM
await fetch('https://gate.whapi.cloud/messages/text', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.WHAPI_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: toWhatsAppJID(phone),
body: `Your subscription is confirmed! Join your private group here:\n${inviteLink}\n\nThis link is personalized -- please do not share it.`,
}),
});
console.log(`Invite link rotated and sent to ${phone}`);
}
Bir değiş tokuş vardır: her rotasyon, henüz tıklamayan abonelere gönderilenler de dahil tüm önceki kodları geçersiz kılar. Doğrudan API eklemesini birincil yol olarak koruyun. Rotasyonu yalnızca standart onboarding'de değil, 403 geri dönüş yolu için kullanın.
Rotasyonu atlayıp statik bir davet URL'si dağıtan projeler, ilk kamuoyu tanıtımından günler içinde hayalet üye modelini raporlar. Ücret ödemeyen her üye içerik tüketir, 1.024 grup üye slotundan birini işgal eder ve ücretli aboneler için algılanan münhasırlığı azaltır. Gelir kaybı kalıcıdır; API düzeyinde katılımcı takibi olmadan hangi üyenin paylaşılan bir bağlantı üzerinden, hangisinin ücretli abonelik üzerinden katıldığını geriye dönük olarak belirlemenin yolu yoktur.
Veritabanında Ne Saklanır (ve Hangi Ölçekte)
Google Sheets 50 üyeyi karşılar; SQLite 50.000'i. Tablonuzun 51. satırı arama hatası üretmeye başlamadan önce lansmandan önce seçin.
Karar tablosu:
| Üye sayısı | Depolama | Kurulum süresi | Idempotency | Taşıma zamanı |
|---|---|---|---|---|
| ≤50 üye | Google Sheets + Apps Script | ~30 dk | Manuel yinelenen kontrol | Aramalarda hatalar görüldüğünde |
| 51--500 üye | SQLite (dosya tabanlı, tek sunucu) | ~2 saat | processed_events tablosu |
Birden fazla sunucuya dağıtım yaptığınızda |
| 500+ üye | PostgreSQL veya MySQL | ~4 saat + hosting | processed_events + indeksli |
Hiçbir zaman — tam 1.024 üyeli grupları kolayca yönetir |
Google Sheets katmanı için bir Google Apps Script web uygulaması, webhook sunucunuzdan POST isteklerini alır ve satır ekler veya günceller. Express sunucunuzun yerel veritabanı yerine çağırdığı herkese açık bir web uygulaması olarak dağıtın:
// Google Apps Script -- deploy as a web app at script.google.com
// Your Express server calls this URL with { action, phone, subscriptionId }
function doPost(e) {
const data = JSON.parse(e.postData.contents);
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Members');
if (data.action === 'add') {
sheet.appendRow([
data.phone,
data.subscriptionId,
data.customerId,
'active',
new Date().toISOString(),
]);
} else if (data.action === 'remove') {
const rows = sheet.getDataRange().getValues();
for (let i = 1; i < rows.length; i++) {
if (rows[i][1] === data.subscriptionId) {
sheet.getRange(i + 1, 4).setValue('removed');
break;
}
}
}
return ContentService.createTextOutput('OK');
}
Hız sınırları bölümündeki SQLite şeması, 51--500 katmanını değişiklik gerektirmeden karşılar. 500+ katmanı için PostgreSQL'e geçin: sütun adları doğrudan aktarılır, SERIAL yerine INTEGER PRIMARY KEY AUTOINCREMENT, unixepoch() yerine EXTRACT(EPOCH FROM NOW()) kullanın. Grubunuz 1.024 üye tavanına ulaştığında, paralel grupları kapsayacak şekilde üye yönlendirme mantığınıza bir GROUP BY group_id sorgusu ekleyin. Bugün 3.000'den fazla işletme Whapi.Cloud'u production'da kullanıyor; bunların arasında 1.024 üyenin ötesine ölçeklenen ücretli topluluk operatörleri de var. Abonelikleriniz yüzlerce kayda ulaştığında, ihtiyacınız olan son şey gece 3'te grup yönetimini bozan bir WhatsApp protokol güncellemesidir; Whapi.Cloud bu protokol değişikliklerini absorbe ederek altta yatan oturum katmanı şeffaf biçimde güncellenirken /groups/{id}/participants'ı kararlı tutar.
Kodsuz Yol: n8n'de Stripe Tetikleyici ve Whapi.Cloud HTTP Düğümü
n8n, Express sunucusunu tamamen ortadan kaldırır: bir Stripe Trigger düğümü, telefon formatı için bir Set düğümü ve n8n üzerinden Whapi.Cloud'a bir HTTP Request düğümü. Normal senaryo için sıfır özel arka uç kodu.
checkout.session.completed için iş akışı:
-
Stripe Trigger düğümü:
checkout.session.completed'ı alacak şekilde yapılandırın. n8n, Stripe panelinize kaydetmeniz için bir webhook URL'si sağlar. -
Set düğümü (telefon dönüşümü): ifade alanı:
{{ $json.data.object.metadata.whatsapp_phone.replace(/^\+/, '') + '@s.whatsapp.net' }}. Tek bir ifadede normalleştirmenin tamamı budur. -
HTTP Request düğümü: Yöntem: POST, URL:
https://gate.whapi.cloud/groups/YOUR_GROUP_ID/participants, Kimlik doğrulama: Generic Credential (Bearer token), Gövde (JSON):{"participants": ["{{ $json.jid }}"]}.
Kaldırma yolu için iş akışını kopyalayın ve Stripe Trigger'ı customer.subscription.deleted'a, HTTP yöntemini DELETE'e çevirin. Değiş tokuş: n8n'de yerel idempotency yoktur. Stripe yeniden denemesi olayı iki kez iletir, üyeyi iki kez ekler veya kaldırır. Yeniden deneme hacminin ihmal edilebilir düzeyde olduğu 50'den az aktif aboneli topluluklar için bu kabul edilebilir. Bu eşiğin üzerinde Node.js + SQLite yolu, processed_events tablosu aracılığıyla tekil kopyalamayı güvenilir biçimde yönetir.
Bu Sistemin Kapsamadıkları (ve Sonraki Adımlar)
Çok katmanlı abonelikler bu rehberde ele alınmamaktadır. Silver ve Gold aboneleri farklı gruplara yönlendirmek, tamamen fiyatlandırma yapınıza bağlı olan katman başına Stripe Product ID arama tablosu ve grup kimlikleri gerektirir.
Ayrıca kapsam dışı: iptalden sonra tolerans süreleri, üyelerin ilk ücretlendirmeden önce katıldığı deneme süresi yönetimi ve resmi WhatsApp Business API'nin 8 üyeli grup tavanı (grup yönetimi erişimi için günlük 100.000'den fazla mesaj gerektirir ve Whapi.Cloud'un web oturumu uygulaması için geçerli değildir). Abonelik tolerans süresi mantığı için üyeler tablonuza bir grace_days sütunu ekleyin ve son kullanma cron kontrolünüzü buna göre ayarlayın.









