Dasturchilar uchun

Postlarni tashkilot saytiga chiqaring

gSocial'da chop etilgan har bir post saytingizning yangiliklar bo'limiga ham tushishi mumkin. Ikki usul bor: gSocial har bir o'zgarishni webhook orqali yuboradi yoki saytingiz postlarni API orqali o'zi o'qiydi.

WebhookPost chop etilishi, tahrirlanishi yoki o'chirilishi bilan saytingizga signature qo'yilgan POST request keladi.
Read APISaytingiz /api/v1/posts endpoint'idan postlarni o'zi o'qiydi, masalan, har bir necha daqiqada yoki cache bilan.

Ulash

  1. Kanallar sahifasida «Website»ni tanlang. Sayt nomini va, webhook kerak bo'lsa, uning https URL'ini kiriting.
  2. Signing secret («Imzo kaliti», whsec_…) faqat bir marta ko'rsatiladi. Uni Sozlamalar › Integratsiyalar sahifasida qayta ko'rish, yangilash va test event yuborish mumkin.
  3. API orqali o'qish uchun o'sha sahifada API key («API kalit», gsk_…) yarating va uni sayt serverida saqlang.

Sozlamalar › Integratsiyalar

Webhook event'lari

Har bir event JSON POST request qilib yuboriladi (Content-Type: application/json). gSocial response'ni 10 soniya kutadi.

EventTavsif
post.publishedPost saytga chiqarildi.
post.updatedChop etilgan post matni yoki rasmlari o'zgardi. data.remote_id — siz qaytargan id.
post.deletedPost saytdan olib tashlanishi kerak. Faqat data.remote_id keladi.
pingSozlamalardagi «Sinov xabarini yuborish» tugmasi yuboradigan test event. 2xx qaytarsangiz yetarli.
Payload namunasi
{
  "id": "6f0c2a8e-3b9d-4c1e-9a55-2d7c1b0e8f41",
  "type": "post.published",
  "created_at": "2026-10-18T09:30:00.000Z",
  "data": {
    "id": "01a14b7e-200f-75da-a03c-dcfd65f9991a",
    "post_id": "01a14b7e-1f9a-7c11-8d2e-6a1f0b9c4e77",
    "title": "Yoshlar markazi ochiladi",
    "language": "uz",
    "html": "<p><strong>1-noyabrdan</strong> bepul to'garaklar ...</p>",
    "text": "1-noyabrdan bepul to'garaklar ...",
    "media": [
      {
        "type": "image",
        "url": "https://smm.example.uz/m/media/…/photo.jpg",
        "mime": "image/jpeg",
        "width": 1280,
        "height": 720,
        "alt": null
      }
    ]
  }
}

Headers

HeaderTavsif
X-GovSocial-Eventevent turi
X-GovSocial-Signaturesignature, pastda qarang
User-Agent: GovSocial-Webhooks/1.0request manbai

Response

2xx status bilan javob bering. Response body'da {"id": "…", "url": "https://…"} qaytarsangiz, gSocial post havolasini panelda ko'rsatadi va keyingi tahrir yoki o'chirishda shu id'ni remote_id sifatida yuboradi.

408, 429, 5xx yoki network xatosida yuborish 3 martagacha retry qilinadi (30 soniya, keyin 60 soniya o'tib). Boshqa 4xx response'lar yakuniy: post panelda xato bilan ko'rinadi. Bitta event ikki marta kelishi mumkin: takrorini event id'si bo'yicha tashlab yuboring.

Webhook URL https bo'lishi (443 yoki 8443 port) va ochiq internetdan ko'rinishi kerak. Private network manzillariga request yuborilmaydi.

Signature'ni tekshirish

Har bir request'da X-gSocial-Signature header'i bor: t — Unix timestamp (soniya), v1 — signing secret bilan "t.body" satridan olingan HMAC-SHA256 (hex).

  • Signature'ni JSON parse qilishdan oldin, request'ning raw body'si bo'yicha tekshiring.
  • 5 daqiqadan eski t qiymatini rad eting (replay attack'dan himoya).
  • Solishtirishni constant-time funksiya bilan bajaring.
  • Signature mos kelmasa, 401 qaytaring.
Node.js (Express)
import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.GOVSOCIAL_SIGNING_SECRET;

// Keep the raw body: the signature covers the exact bytes.
app.post("/govsocial/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("X-GovSocial-Signature") ?? "";
  const parts = Object.fromEntries(header.split(",").map((part) => part.split("=", 2)));
  const t = Number(parts.t);
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(`${t}.${req.body}`)
    .digest("hex");
  const valid =
    Math.abs(Date.now() / 1000 - t) <= 300 &&
    typeof parts.v1 === "string" &&
    parts.v1.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
  if (!valid) return res.status(401).end();

  const event = JSON.parse(req.body.toString("utf8"));
  // event.type: post.published | post.updated | post.deleted | ping
  res.json({ id: "news-123", url: "https://example.uz/news/123" });
});
PHP
<?php
$secret = getenv('GOVSOCIAL_SIGNING_SECRET');
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_GOVSOCIAL_SIGNATURE'] ?? ''), $sig);
$t = (int) ($sig['t'] ?? 0);
$expected = hash_hmac('sha256', $t . '.' . $body, $secret);

if (abs(time() - $t) > 300 || !hash_equals($expected, $sig['v1'] ?? '')) {
    http_response_code(401);
    exit;
}

$event = json_decode($body, true);
// $event['type']: post.published | post.updated | post.deleted | ping
header('Content-Type: application/json');
echo json_encode(['id' => 'news-123', 'url' => 'https://example.uz/news/123']);
Python (Flask)
import hashlib, hmac, os, time
from flask import Flask, abort, jsonify, request

app = Flask(__name__)
SECRET = os.environ["GOVSOCIAL_SIGNING_SECRET"].encode()

@app.post("/govsocial/webhook")
def govsocial_webhook():
    body = request.get_data()  # raw bytes
    header = request.headers.get("X-GovSocial-Signature", "")
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    t = int(parts.get("t", "0"))
    expected = hmac.new(SECRET, f"{t}.".encode() + body, hashlib.sha256).hexdigest()
    if abs(time.time() - t) > 300 or not hmac.compare_digest(expected, parts.get("v1", "")):
        abort(401)

    event = request.get_json()
    # event["type"]: post.published | post.updated | post.deleted | ping
    return jsonify(id="news-123", url="https://example.uz/news/123")

Read API

Har bir request'ga Authorization: Bearer gsk_… header'ini qo'shing. API key'ni faqat serverda saqlang, brauzer kodiga qo'ymang.

GET /api/v1/posts

Saytga chiqarilgan postlar, yangilari birinchi.

ParameterTavsif
limit1–100, default 20
cursoroldingi response'dagi next_cursor
channelbir nechta sayt ulangan bo'lsa, channel id
curl
curl -H "Authorization: Bearer gsk_…" \
  "https://gsocial.uz/api/v1/posts?limit=20"
Response
{
  "data": [
    {
      "id": "01a14b7e-200f-75da-a03c-dcfd65f9991a",
      "post_id": "01a14b7e-1f9a-7c11-8d2e-6a1f0b9c4e77",
      "title": "Yoshlar markazi ochiladi",
      "html": "<p>…</p>",
      "text": "…",
      "hashtags": ["yoshlar"],
      "language": "uz",
      "media": [],
      "url": "https://example.uz/news/123",
      "channel": { "id": "01a14b54-…", "name": "Rasmiy sayt" },
      "published_at": "2026-10-18T09:30:00.000Z",
      "updated_at": "2026-10-18T09:30:02.000Z"
    }
  ],
  "next_cursor": "WyIyMDI2LTEwLTE4VDA5OjMwOjAwLjAwMFoiLCIwMWExNGI3ZSJd"
}

GET /api/v1/posts/:id

id — ro'yxatdagi yoki webhook'dagi id. Topilmasa 404.

curl
curl -H "Authorization: Bearer gsk_…" \
  "https://gsocial.uz/api/v1/posts/01a14b7e-200f-75da-a03c-dcfd65f9991a"
Response
{ "data": { "id": "01a14b7e-…", "title": "…", "html": "<p>…</p>" } }

Post field'lari

FieldTavsif
idSaytdagi post nusxasining id'si (webhook'dagi data.id bilan bir xil)
post_idgSocial'dagi umumiy post id'si
titleSarlavha yoki null
htmlHTML formatdagi matn: <p>, <strong>, <em>, <s>, <code>, <a>, <blockquote>, <br>
textFormatlashsiz plain text
hashtagsHeshteglar (# belgisiz)
languageuz, ru yoki en
mediaRasm va videolar: type, url, mime, width, height, alt
urlSaytingiz qaytargan havola yoki null
channelSayt kanali: id va name
published_atChop etilgan vaqt (ISO 8601, UTC)
updated_atOxirgi o'zgarish vaqti

Xatolar va rate limit

StatusTavsif
400limit noto'g'ri (invalid_limit)
401API key yo'q, noto'g'ri yoki bekor qilingan (unauthorized)
404Post topilmadi (not_found)
429Daqiqasiga 120 request'dan oshdi; Retry-After header'idagi soniyani kuting (rate_limited)
Response
HTTP/1.1 429 Too Many Requests
Retry-After: 17
Content-Type: application/json

{ "error": "rate_limited" }

Savollar bo'lsa, operatorga Telegram'da yozing.

API va webhook'lar · gSocial dasturchilar uchun