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.
Ulash
- Kanallar sahifasida «Website»ni tanlang. Sayt nomini va, webhook kerak bo'lsa, uning https URL'ini kiriting.
- Signing secret («Imzo kaliti», whsec_…) faqat bir marta ko'rsatiladi. Uni Sozlamalar › Integratsiyalar sahifasida qayta ko'rish, yangilash va test event yuborish mumkin.
- API orqali o'qish uchun o'sha sahifada API key («API kalit», gsk_…) yarating va uni sayt serverida saqlang.
Webhook event'lari
Har bir event JSON POST request qilib yuboriladi (Content-Type: application/json). gSocial response'ni 10 soniya kutadi.
| Event | Tavsif |
|---|---|
| post.published | Post saytga chiqarildi. |
| post.updated | Chop etilgan post matni yoki rasmlari o'zgardi. data.remote_id — siz qaytargan id. |
| post.deleted | Post saytdan olib tashlanishi kerak. Faqat data.remote_id keladi. |
| ping | Sozlamalardagi «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
| Header | Tavsif |
|---|---|
| X-GovSocial-Event | event turi |
| X-GovSocial-Signature | signature, pastda qarang |
| User-Agent: GovSocial-Webhooks/1.0 | request 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.
| Parameter | Tavsif |
|---|---|
| limit | 1–100, default 20 |
| cursor | oldingi response'dagi next_cursor |
| channel | bir 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
| Field | Tavsif |
|---|---|
| id | Saytdagi post nusxasining id'si (webhook'dagi data.id bilan bir xil) |
| post_id | gSocial'dagi umumiy post id'si |
| title | Sarlavha yoki null |
| html | HTML formatdagi matn: <p>, <strong>, <em>, <s>, <code>, <a>, <blockquote>, <br> |
| text | Formatlashsiz plain text |
| hashtags | Heshteglar (# belgisiz) |
| language | uz, ru yoki en |
| media | Rasm va videolar: type, url, mime, width, height, alt |
| url | Saytingiz qaytargan havola yoki null |
| channel | Sayt kanali: id va name |
| published_at | Chop etilgan vaqt (ISO 8601, UTC) |
| updated_at | Oxirgi o'zgarish vaqti |
Xatolar va rate limit
| Status | Tavsif |
|---|---|
| 400 | limit noto'g'ri (invalid_limit) |
| 401 | API key yo'q, noto'g'ri yoki bekor qilingan (unauthorized) |
| 404 | Post topilmadi (not_found) |
| 429 | Daqiqasiga 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.