وثائق المطورين

حزمة رسمية للـ TypeScript وأخرى لـ PHP. نفس العقد، نفس المسارات، نفس الصلاحيات التي يفحصها تطبيق قُرب نفسه.

TypeScript1.7.0PHP1.7

الويب هوك

مئة حدث عبر تسعة وعشرين نطاقاً. سجّل عنواناً، وقُرب يرسل إليه عند وقوع الحدث بدل أن تستعلم أنت في حلقة.

100 حدث · 29 نطاقاً

التوقيع — معيار Standard Webhooks

كل تسليم يحمل ثلاث ترويسات، والتوقيع HMAC-SHA256 على «المعرّف.الطابع الزمني.الجسم». تحقّق منه دائماً: عنوان غير محقَّق يقبل أي طلب من أي جهة تعرف رابطه.

الترويسات
webhook-idULID — dedupe on this
webhook-timestampUnix seconds
webhook-signaturev1,<base64 HMAC-SHA256>

التحقق

استخدم مكتبة المعيار بدل كتابة المقارنة بيدك — المقارنة الساذجة عرضة لهجوم توقيت، والمكتبة تستخدم مقارنة ثابتة الزمن.

ts
import { Webhook } from 'standardwebhooks'
import express from 'express'

const app = express()

app.post(
  '/gurb/webhook',
  // RAW body, not express.json(). See the note below.
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const wh = new Webhook(process.env.GURB_WEBHOOK_SECRET!)

    let event
    try {
      event = wh.verify(req.body, {
        'webhook-id': req.header('webhook-id')!,
        'webhook-timestamp': req.header('webhook-timestamp')!,
        'webhook-signature': req.header('webhook-signature')!,
      })
    } catch {
      return res.status(400).send('bad signature')
    }

    // Answer FIRST, work after. A slow handler is a failed delivery, and a
    // failed delivery is retried — so slow work becomes duplicate work.
    res.sendStatus(200)
    void handle(event)
  },
)
php
use StandardWebhooks\Webhook;

$raw = file_get_contents('php://input');   // RAW, before any json_decode
$wh  = new Webhook(getenv('GURB_WEBHOOK_SECRET'));

try {
    $event = $wh->verify($raw, [
        'webhook-id'        => $_SERVER['HTTP_WEBHOOK_ID'],
        'webhook-timestamp' => $_SERVER['HTTP_WEBHOOK_TIMESTAMP'],
        'webhook-signature' => $_SERVER['HTTP_WEBHOOK_SIGNATURE'],
    ]);
} catch (\Throwable $e) {
    http_response_code(400);
    exit;
}

تحقّق من الجسم الخام لا من المُفكّك

وقّع قُرب على البايتات التي أرسلها. لو فكّكت JSON ثم أعدت تسلسله لتتحقق، فقد غيّرت المسافات وترتيب المفاتيح والتوقيع لن يطابق — وستطارد «توقيعاً خاطئاً» وهو سليم. اقرأ الجسم الخام أولاً.

نافذة إعادة التشغيل ٣٠٠ ثانية

تسليم أقدم من خمس دقائق يُرفض. فساعة خادمك لازم تكون مضبوطة؛ الانحراف يظهر كفشل توقيع متقطّع لا كخطأ وقت.

إعادة المحاولة والتكرار

التسليم الفاشل يُعاد. فقد يصلك الحدث نفسه مرتين — استخدم webhook-id لإزالة التكرار، وتعامل مع معالجك كأنه قد يُنادى أكثر من مرة على نفس الحدث.

الأحداث تُطلق بعد إتمام الكتابة

لا تُطلق داخل معاملة قابلة للتراجع، لأن الويب هوك لا يمكن سحبه بعد إرساله. فوصول الحدث يعني أن التغيير ثابت فعلاً.

الكتالوج

كل اسم هنا يظهر في واجهة الاشتراك عند عميلك، وكلها موصولة بمواضع إطلاق حقيقية — اسم معلن بلا إطلاق وعد مكسور، ويوجد فحص آلي يمنع ذلك.

advertisement

  • advertisement.created
  • advertisement.deleted
  • advertisement.updated

album

  • album.created
  • album.deleted
  • album.photo_added
  • album.photo_deleted
  • album.updated

api_key

  • api_key.created
  • api_key.revoked

app

  • app.installed
  • app.reinstated
  • app.suspended
  • app.uninstalled

award

  • award.created
  • award.deleted
  • award.granted
  • award.revoked

blog

  • blog.deleted
  • blog.published
  • blog.unpublished
  • blog.updated

comment

  • comment.created
  • comment.deleted

community

  • community.settings_changed
  • community.updated
  • community.visibility_changed

consultant

  • consultant.created
  • consultant.status_changed
  • consultant.updated

custom_domain

  • custom_domain.activated
  • custom_domain.added
  • custom_domain.removed
  • custom_domain.verified

event

  • event.attendee_joined
  • event.attendee_left
  • event.cancelled
  • event.created
  • event.deleted
  • event.updated

family_member

  • family_member.added
  • family_member.deleted
  • family_member.updated

family_tree_request

  • family_tree_request.approved
  • family_tree_request.created
  • family_tree_request.rejected

group

  • group.created
  • group.deleted
  • group.member_added
  • group.member_removed
  • group.updated

group_join_request

  • group_join_request.approved
  • group_join_request.created
  • group_join_request.rejected

invitation

  • invitation.accepted
  • invitation.revoked
  • invitation.sent

invite_link

  • invite_link.created
  • invite_link.revoked

join_request

  • join_request.approved
  • join_request.created
  • join_request.rejected

leave_request

  • leave_request.approved
  • leave_request.cancelled
  • leave_request.created
  • leave_request.rejected

member

  • member.banned
  • member.joined
  • member.left
  • member.permissions_changed
  • member.removed
  • member.restricted
  • member.restriction_lifted
  • member.role_changed
  • member.unbanned

poll

  • poll.voted

post

  • post.created
  • post.deleted
  • post.pinned
  • post.updated

project

  • project.created
  • project.status_changed
  • project.updated

project_request

  • project_request.created

report

  • report.created
  • report.dismissed
  • report.resolved

subscription

  • subscription.activated
  • subscription.cancelled
  • subscription.plan_changed

subscription_request

  • subscription_request.approved
  • subscription_request.created
  • subscription_request.rejected

support_conversation

  • support_conversation.opened

task

  • task.assigned
  • task.completed
  • task.created
  • task.deleted
  • task.reopened
  • task.updated