İçeriğe geç

Mosaqo webhook’ları

Abone olduğunuz her olay için endpoint’inize imzalı JSON gövdesiyle bir POST gönderir, siz kabul edene kadar artan aralıklarla yeniden deneriz.

Abone olma

Ya Bulk & API bölümünden bir endpoint ekleyin ya da webhooks:write kapsamlı bir anahtarla kendi kodunuzdan kaydedin — bir otomasyon platformu bir tetikleyiciyi tam olarak böyle açıp kapatır:

curl -X POST https://api.mosaqo.app/v1/public-api/webhooks \
  -H "Authorization: Bearer $MOSAQO_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/mosaqo",
    "eventTypes": ["qr.published", "qr.updated"]
  }'

Yanıt imzalama secret değerini içerir. Yalnızca bir kez gösterilir — saklayın ya da aboneliği sonradan değiştirin.

Olaylar

OlayNe zaman tetiklenir
qr.createdUygulamada veya API üzerinden bir kod oluşturulduğunda
qr.publishedBir kod yayına girip yönlendirmesi yanıt vermeye başladığında
qr.updatedİçerik, tasarım, ad veya durum değiştiğinde
qr.archivedBir kod arşivlenip yönlendirme sunucusundan kaldırıldığında
qr.deletedBir kod kalıcı olarak silindiğinde
redirect.rule_changedBir hedef veya hedefleme kuralı değiştiğinde
scan.aggregate_readyBir tarama toplaması hazır olduğunda
export.readyAsenkron bir dışa aktarma bittiğinde
approval.changedBir inceleme onaylandığında veya reddedildiğinde
bulk.completedToplu bir iş tamamlandığında

Payload

Bir olay QR koduyla ilgiliyse data.qr, GET /qr/{id} çağrısının döndürdüğü özetin aynısını taşır — yani tetikleyicinin, kendisine az önce bildirilen kaydı geri sorması gerekmez.

{
  "id": "0b0f…",
  "type": "qr.published",
  "workspaceId": "2f60…",
  "occurredAt": "2026-08-05T10:20:30.456Z",
  "data": {
    "qrId": "e7b7…",
    "source": "public_api",
    "qr": {
      "id": "e7b7…",
      "name": "Autumn campaign",
      "slug": "autumn-campaign-1a2b3c4d",
      "mode": "dynamic",
      "contentType": "url",
      "status": "published",
      "approvalState": "not_required",
      "folderId": null,
      "updatedAt": "2026-08-05T10:20:30.401Z",
      "publicUrl": "https://mosaqo.link/aB3dEf"
    }
  }
}

Bir teslimatı doğrulama

Her istek bir zaman damgası ve hem damgayı hem gövdeyi kapsayan bir imza taşır:

X-Mosaqo-Timestamp: 1785942718
X-Mosaqo-Signature: t=1785942718,v1=<hex HMAC-SHA256 of "{timestamp}.{raw body}">
X-Mosaqo-Delivery: 0b0f…
X-Mosaqo-Event: qr.published

Zaman damgası kendi saatinizden uzak olan her şeyi reddetmek, yakalanmış bir teslimatı yeniden oynatılamaz kılan şeydir; bu kontrolü atlamayın. İmzaları sabit sürede karşılaştırın ve isteğin ham gövdesini kullanın: ayrıştırılmış JSON’u yeniden serileştirmek baytları değiştirir ve imza artık tutmaz.

Node.js

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyMosaqoWebhook(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(
    header.split(',').map((piece) => piece.split('=').map((s) => s.trim())),
  );
  const timestamp = Number(parts.t);
  if (!Number.isFinite(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;

  const expected = createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest();
  const received = Buffer.from(parts.v1 ?? '', 'hex');
  return expected.length === received.length && timingSafeEqual(expected, received);
}

PHP

<?php
function verify_mosaqo_webhook(string $rawBody, string $header, string $secret, int $tolerance = 300): bool {
    $parts = [];
    foreach (explode(',', $header) as $piece) {
        [$key, $value] = array_map('trim', explode('=', $piece, 2));
        $parts[$key] = $value;
    }
    if (!isset($parts['t'], $parts['v1'])) return false;
    if (abs(time() - (int) $parts['t']) > $tolerance) return false;

    $expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret);
    return hash_equals($expected, $parts['v1']);
}

Python

import hmac, time
from hashlib import sha256

def verify_mosaqo_webhook(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(piece.strip().split("=", 1) for piece in header.split(","))
    if "t" not in parts or "v1" not in parts:
        return False
    if abs(time.time() - int(parts["t"])) > tolerance:
        return False

    signed = f"{parts['t']}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Yeniden denemeler ve hatalar

Olayı kaydeder kaydetmez herhangi bir 2xx ile yanıt verin; işi sonra yapın. Üstel artan aralıklarla altı kez yeniden deneriz, dolayısıyla yavaş bir işleyici yinelenen teslimatlara dönüşür — X-Mosaqo-Delivery değerini kendi tarafınızda idempotentlik anahtarı olarak kullanın.

Endpoint’ler herkese açık bir sunucuda HTTPS olmalıdır. Yerel ve özel adresler abonelik oluşturulurken zaten reddedilir.

Kullanımdan kaldırılan imza

X-Mosaqo-Signature-V0, yalnızca gövde üzerinden hesaplanan daha eski bir imzayı taşır. Yineleme korumasını ifade edemez, mevcut alıcılar geçiş yapsın diye bir sürüm daha gönderilir ve yeni hiçbir şeyde kullanılmamalıdır.

Tarama verileri dışarı gönderilmez

Bilinçli olarak tarama başına webhook yoktur. Mosaqo tarama düzeyindeki analitiği üçüncü taraf endpoint’lerine göndermez ve yukarıdaki tablodaki hiçbir olay bunu taşımaz.

Tarama geçmişi yalnızca kendi anahtarınızla siz çekerseniz erişilebilir — GET /scans — ve o zaman bile çalışma alanınızın analitik gizlilik ayarlarına tabidir: kapattığınız boyutlar hiçbir satırda görünmez, bot filtresi geçerlidir ve “benzersiz taramalar” ın arkasındaki takma adlı ziyaretçi özeti asla dışa verilmez. Yanıt, ne aldığınızı bilmeniz için allowedDimensions listeler.

scan.aggregate_ready bir toplamanın hazır olduğunu söyler; taramaların kendisini içermez. Entegrasyonunuzun gerçekten gerçek zamanlı tek tek taramalara ihtiyacı varsa bu talep üzerine mümkündür — var olduğunu varsaymak yerine bizimle konuşun.

Webhook alamıyorsanız

Bunun yerine siz sorun. GET /qr?updatedSince=… değişen kodları verir, GET /scans?since=… ise tarama geçmişini kararlı bir imleçle ileriye doğru gezer.