Webhook, bir şey olduğunda GREM'in URL'nize gönderdiği bir POST isteğidir: bir iş biter, bir ilan veya lead değişir. Abonelik yönetimi webhooks kapsamını gerektirir ve ücretsizdir. Bir ajansın webhook'ları ajans genelindeki anahtarla yönetilir; temsilciye bağlı bir anahtar 403 alır.
Abone olma
curl -X POST https://developers.grem.capital/api/v1/webhooks \
-H "Authorization: Bearer gsk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/grem-hook","events":["content.completed","content.failed","lead.created"]}'
{ "id": "...", "url": "https://example.com/grem-hook", "events": [...], "status": "active",
"failureCount": 0, "lastDeliveryAt": null, "secret": "whsec_..." }
url: varsayılan bağlantı noktasında (443) herkese açık birhttps://adresi, içindeuser:passwordolmadan. Başka bağlantı noktaları, adreste kimlik bilgileri, özel, yerel ve iç adresler reddedilir (400). Bağlantı noktası kuralı bir adres belirlediğinizde uygulanır: daha önce başka bir bağlantı noktasıyla kaydedilmiş adresler teslim almaya devam eder.events: aşağıdaki listeden en az bir olay; bilinmeyen adlar atılır.secret: imzalama gizli anahtarı. Yalnızca bir kez döndürülür: saklayın.- Hesap başına en fazla 10 adres ve ayrıca test anahtarlarıyla oluşturulan en fazla 10 sandbox adresi; 11. adres
422 business_ruleile reddedilir. - Test anahtarıyla oluşturulan adresler sandbox adresleridir: yalnızca sandbox işlerinin olaylarını alırlar (Sandbox).
Abonelikler panelden de yönetilebilir: API → Webhook'lar. İmzalama gizli anahtarını da orada değiştirebilirsiniz.
Yönetim
| İstek | Ne yapar |
|---|---|
GET /webhooks | abonelikleriniz (gizli anahtarlar olmadan) |
PATCH /webhooks/{id} | url, events veya status (active / disabled) değiştirme |
DELETE /webhooks/{id} | aboneliği silme |
POST /webhooks/{id}/test | deneme olayı gönderme; {"eventType": "content.completed"} gövdesi biçimi belirler |
GET /webhooks/{id}/deliveries | son 50 teslim: durum, yanıt kodu, gecikme, yanıtın başı |
POST /webhooks/{id}/deliveries/{deliveryId}/resend | teslimi yeniden gönderme; deliveryId, kayıttaki bir satırın _id değeridir |
events içinde geçerli hiçbir olay olmayan veya status değeri active / disabled dışında olan PATCH 400 ile reddedilir. Bilinmeyen bir aboneliğin DELETE isteği 404 döndürür. Teslim kaydında payload, olayın data alanıdır.
Olaylar
Biten işler
| Olay | Ne zaman |
|---|---|
valuation.completed, valuation.failed | tam değerleme raporu |
review.completed, review.failed | rapor incelemesi |
content.completed, content.partial, content.failed | içerik paketi (partial: bazı dosyalar başarısız) |
text.completed, text.failed | metinler |
video.completed, video.failed | video |
avatar.completed, avatar.failed | avatar videosu |
photo.completed, photo.failed | fotoğraf iyileştirme |
floorplan.completed, floorplan.failed | kat planı |
pdf.completed, pdf.failed | PDF sunum |
data: { "jobId", "status", "state" }. status, aracın büyük harfli ham durumudur (örneğin COMPLETED); karar için state kullanın. Bir iş 30 dakika içinde bitmezse "reason": "timeout" ile .failed. Bir iş yalnızca oluşturulduğunda olayına ait bir abonelik varsa izlenir (İşler ve sonuçlar).
İlanlar
| Olay | Ne zaman |
|---|---|
object.created | bir ilan oluşturdunuz |
object.updated | bir ilan değişti, moderatör tarafından da |
object.published | bir ilan herkese açık oldu |
object.deleted | bir taslak silindi veya yayınlanmış bir ilan arşivlendi |
data: { "objectId", "kind", "status", "occurredAt" }; object.deleted içinde status yerine mode (deleted veya archived). Tam veriler: GET /objects/{kind}/{id}.
Lead'ler
| Olay | Ne zaman |
|---|---|
lead.created | size yeni bir lead |
lead.updated | durum veya sorumlu değişti |
data: { "leadId", "status", "type", "source", "objectId", "occurredAt" }. Olayda müşterinin kişisel verileri yoktur: lead GET /leads/{id} ile okunur.
Swipe Match
| Olay | Ne zaman |
|---|---|
match.lead.offered | size (veya ajansınızın bir temsilcisine) bir lead teklif edildi |
match.lead.offer_closed | teklif siz olmadan kapandı (süre doldu) |
match.lead.matched | lead size atandı |
match.lead.updated | lead yeni bir aşamaya geçti |
match.lead.unassigned | lead sizden geri alındı |
data: { "leadId", "eventId", "eventCode", "occurredAt", "source", "stage", "active", "leadKind", "clientType", "country", "brokerId", "agencyId" }. Müşterinin adı, telefonu ve e-postası olaylarda asla yer almaz (Swipe Match).
Teslim biçimi
POST https://example.com/grem-hook
Content-Type: application/json
x-grem-event: content.completed
x-grem-signature: t=1759400000,v1=3b78a03b78058d229023db2a0b4ee072...
{ "id": "...", "type": "content.completed", "createdAt": "2026-10-02T10:00:00.000Z", "data": { ... } }
id, bir teslimin tüm yeniden denemelerinde aynıdır: bir olayı iki kez işlememek için kullanın.tvecreatedAtilgili denemenin zamanıdır.- Listeden bir
eventTypeile yapılan test teslimi (POST /webhooks/{id}/test)dataiçindetest: truetaşır;eventTypeolmadan olay türüwebhook.testolur. Listede olmayan bireventType:400.webhook.testdeğerinin kendisi kabul edilir.
İmzayı doğrulama
İmza, abonelik gizli anahtarıyla "<t>.<ham gövde>" dizesinin HMAC-SHA256 değeridir, v1 içinde onaltılıktır. Herhangi bir işlemden önce doğrulayın ve eski t değerlerini reddedin (yeniden oynatmaya karşı koruma).
İmzayı JSON'u ayrıştırmadan önce, geldiği gibi ham gövde üzerinden hesaplayın. Yeniden serileştirme baytları değiştirir ve imza eşleşmez.
const crypto = require('crypto');
function verifyGremSignature(secret, header, rawBody, toleranceSec = 300) {
const parts = Object.fromEntries(String(header || '').split(',').map((kv) => kv.split('=')));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(parts.v1 || '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post('/grem-hook', express.raw({ type: 'application/json' }), (req, res) => {
const raw = req.body.toString('utf8');
if (!verifyGremSignature(process.env.GREM_WEBHOOK_SECRET, req.get('x-grem-signature'), raw)) {
return res.status(400).send('invalid signature');
}
const event = JSON.parse(raw);
// event.id, event.type, event.data
res.sendStatus(200);
});
SDK içinde bunu verifyWebhookSignature() yapar.
Yeniden denemeler ve devre dışı bırakma
- Sunucunuz 10 saniye içinde
2xxile yanıt verirse teslim başarılıdır. Hızlı yanıt verin, ağır işi arka planda yapın. - Başarısız bir teslim 5 dakika, 30 dakika, 2 saat ve 6 saat sonra yeniden denenir: toplam en fazla 5 deneme.
- Art arda tamamen başarısız 5 teslimden sonra abonelik devre dışı bırakılır (
status: disabled).PATCH /webhooks/{id}ve{"status":"active"}ile veya panelden yeniden etkinleştirin. - Teslim günlüğü (
GET /webhooks/{id}/deliveries) sunucunuzun yanıt kodunu ve yanıt gövdesinin başını gösterir: bir teslimin neden başarısız olduğunu buradan görürsünüz.
İlgili makaleler
- İşler ve sonuçlar: iş durumları.
- Swipe Match: modül lead'lerinin olay kodları.