Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ evals/results/
.cursor/
.mcp.json
.playwright-mcp/
.pi/tmp/

# C# / .NET
bin/
Expand Down
35 changes: 35 additions & 0 deletions apps/docs/components/mdx/mdx-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,41 @@ export async function ApiCodeExample({
return <GuideCodeBlock language={lang} code={code} />;
}

// Multi-language SDK-only mode: render CodeExamples-style dropdown from examples.json.
if (!operationId && operations?.length) {
const sdkLangs = ['typescript', 'python', 'go', 'kotlin', 'swift', 'csharp'] as const;
type SdkLang = (typeof sdkLangs)[number];
const isSdkLang = (value: string): value is SdkLang =>
(sdkLangs as readonly string[]).includes(value);
const requestedSdkLangs = langs?.filter(isSdkLang);
const visibleSdkLangs = requestedSdkLangs?.length ? requestedSdkLangs : [...sdkLangs];
const sdkDefaultLang = defaultLang && isSdkLang(defaultLang) ? defaultLang : undefined;
const sdkExamples = Object.fromEntries(
sdkLangs.map((sdkLang) => [sdkLang, getSdkExampleForLang(sdkLang, operations, showInit)])
);
const syntheticEndpoint = {
id: operations[0]?.id ?? 'sdk-example',
method: 'GET' as const,
path: '',
tags: [],
title: title ?? 'SDK example',
parameters: [],
responses: { '200': { description: 'OK' } },
};

return (
<CodeExamples
endpoint={syntheticEndpoint}
show="request"
title={title}
sdkExamples={sdkExamples}
langs={visibleSdkLangs}
defaultLang={sdkDefaultLang}
className="my-4"
/>
);
}

// Multi-language mode: render CodeExamples with dropdown
if (!operationId) {
return (
Expand Down
35 changes: 14 additions & 21 deletions apps/docs/content/guides/webhook/polling.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Поллинг
description: "Поллинг исходящих вебхуков Пачки через API: получение накопленных событий, удаление обработанных, пример кода для локальной разработки и сред с жёсткими firewall-правилами"
description: "Поллинг исходящих вебхуков Пачки через SDK: получение новых событий без публичного webhook URL, дедупликация доставок и пример воркера для локальной разработки"
related:
- /guides/webhook/events
- /guides/webhook/handler
Expand All @@ -9,31 +9,24 @@ related:

# Поллинг

Если у вас нет возможности принимать входящие HTTP-запросы (локальная разработка, жёсткие firewall-правила), используйте **поллинг** — получение событий через API.
Если у вас нет возможности принимать входящие HTTP-запросы (локальная разработка, жёсткие firewall-правила), используйте **поллинг** — получение событий через API или готовые helpers в SDK.

Включите настройку **Сохранять историю событий** на вкладке **Исходящий Webhook** в настройках бота (подробнее — в разделе [Настройка и типы событий](/guides/webhook/events#obschie-nastroiki)), чтобы получать события через API:
Включите настройку **Сохранять историю событий** на вкладке **Исходящий Webhook** в настройках бота (подробнее — в разделе [Настройка и типы событий](/guides/webhook/events#obschie-nastroiki)), чтобы получать события через метод [История событий](GET /webhooks/events).

- [Список событий бота](GET /webhooks/events) — получить накопленные события
- [Удалить событие](DELETE /webhooks/events/{id}) — удалить обработанное событие из очереди
<Info>SDK helpers по умолчанию начинают читать только события, созданные после запуска воркера, и дедуплицируют уже обработанные delivery ID в памяти.</Info>

<Info>Периодически запрашивайте список событий, обрабатывайте каждое и удаляйте обработанные.</Info>
## Пример поллинга

## Пример поллинга (TypeScript)
<ApiCodeExample title="Polling событий" operations={[
{ id: "BotOperations_getWebhookEvents_pollWebhookEvents" }
]} langs={["typescript", "python", "go", "kotlin", "swift", "csharp"]} />

```typescript
import { PachcaClient } from "@pachca/sdk"
Если вам нужен только payload вебхука, используйте helper для polling payload'ов:

const client = new PachcaClient("YOUR_BOT_TOKEN")
<ApiCodeExample title="Polling payload'ов" operations={[
{ id: "BotOperations_getWebhookEvents_pollWebhookPayloads" }
]} langs={["typescript", "python", "go", "kotlin", "swift", "csharp"]} />

async function pollEvents() {
const events = await client.bots.getWebhookEvents()
for (const event of events.data) {
console.log("Событие:", event.type, event.event)
// Обработать событие...
await client.bots.deleteWebhookEvent(event.id) // Удалить из очереди
}
}
## Ручная работа через API

// Запускать каждые 5 секунд
setInterval(pollEvents, 5000)
```
SDK helpers используют метод [История событий](GET /webhooks/events) и курсорную пагинацию. Если вы пишете собственный polling loop без SDK, периодически запрашивайте список событий, обрабатывайте новые delivery ID и храните дедупликацию на своей стороне. Метод [Удаление события](DELETE /webhooks/events/{id}) можно использовать для ручной очистки обработанных записей истории.
10 changes: 10 additions & 0 deletions apps/docs/content/updates/2026-06-05.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
date: "2026-06-05"
title: "Webhook-модели и polling в SDK"
---

SDK стали точнее типизировать payload вебхуков и получили helpers для polling истории событий. Это упрощает обработку вебхуков без публичного webhook URL и сохраняет discriminator-поля для маршрутизации событий в коде.

Был обновлен следующий метод:

- [История событий](GET /webhooks/events)
30 changes: 30 additions & 0 deletions apps/docs/data/releases.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,34 @@
[
{
"product": "sdk",
"version": "1.0.21",
"date": "2026-06-05",
"changes": [
{
"type": "~",
"description": "SDK сохраняют литеральные поля webhook-моделей, включая `type` и дополнительный дискриминатор `event`"
},
{
"type": "+",
"description": "SDK получили helpers для polling истории вебхуков и примеры запуска без публичного webhook URL"
}
]
},
{
"product": "generator",
"version": "1.1.6",
"date": "2026-06-05",
"changes": [
{
"type": "~",
"description": "Генераторы сохраняют литеральные поля моделей, включая основной дискриминатор `type` и дополнительные литералы вроде `event`"
},
{
"type": "+",
"description": "Генераторы выпускают SDK helpers для polling истории вебхуков во всех поддерживаемых языках"
}
]
},
{
"product": "sdk",
"version": "1.0.20",
Expand Down
44 changes: 34 additions & 10 deletions apps/docs/lib/mdx-expander.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,25 +559,35 @@ export async function expandMdxComponents(content: string): Promise<string> {
const apiCodeRegex = /<ApiCodeExample\s+([\s\S]*?)\/>/g;
const apiCodeMatches = [...result.matchAll(apiCodeRegex)];

const SDK_LANGS = ['typescript', 'python', 'go', 'kotlin', 'swift', 'csharp'];
const SDK_LANGS = ['typescript', 'python', 'go', 'kotlin', 'swift', 'csharp'] as const;
type SdkLang = (typeof SDK_LANGS)[number];
const isSdkLang = (value: string): value is SdkLang =>
(SDK_LANGS as readonly string[]).includes(value);

for (const match of apiCodeMatches) {
const [fullMatch, attrs] = match;
const lang = attrs.match(/lang="([^"]+)"/)?.[1];
const operationId = attrs.match(/operationId="([^"]+)"/)?.[1];
const title = attrs.match(/title="([^"]+)"/)?.[1];
const paramsMatch = attrs.match(/params=\{\{([^}]*)\}\}/);
const langsMatch = attrs.match(/langs=\{\s*\[([\s\S]*?)\]\s*\}/);
const requestedSdkLangs = langsMatch
? [...langsMatch[1].matchAll(/["']([^"']+)["']/g)]
.map((langMatch) => langMatch[1])
.filter(isSdkLang)
: [];
const sdkLangsForBlock = requestedSdkLangs.length > 0 ? requestedSdkLangs : SDK_LANGS;

const ops: Array<{ id: string; comment?: string }> = [];
const opRegex = /id:\s*"([^"]+)"(?:,\s*comment:\s*"([^"]*)")?/g;
let opMatch;
while ((opMatch = opRegex.exec(attrs)) !== null) {
ops.push({ id: opMatch[1], comment: opMatch[2] });
}
if (operationId) ops.push({ id: operationId });

// SDK language mode: generate from examples.json
if (lang && SDK_LANGS.includes(lang)) {
const ops: Array<{ id: string; comment?: string }> = [];
const opRegex = /id:\s*"([^"]+)"(?:,\s*comment:\s*"([^"]*)")?/g;
let opMatch;
while ((opMatch = opRegex.exec(attrs)) !== null) {
ops.push({ id: opMatch[1], comment: opMatch[2] });
}
if (operationId) ops.push({ id: operationId });

if (lang && isSdkLang(lang)) {
const showInit = !attrs.includes('showInit={false}');
const code = getSdkExampleForLang(lang, ops, showInit);

Expand All @@ -592,6 +602,20 @@ export async function expandMdxComponents(content: string): Promise<string> {
continue;
}

// Multi-language SDK-only mode: render all SDK examples in markdown output.
if (!lang && !operationId && ops.length > 0) {
const showInit = !attrs.includes('showInit={false}');
let md = '';
if (title) md += `**${title}**\n\n`;
for (const sdkLang of sdkLangsForBlock) {
const code = getSdkExampleForLang(sdkLang, ops, showInit);
if (!code) continue;
md += `### ${sdkLang}\n\n\`\`\`${sdkLang}\n${code}\n\`\`\`\n\n`;
}
result = result.replace(fullMatch, md);
continue;
}

// curl/cli mode: generate from endpoint
if (!operationId) {
result = result.replace(fullMatch, '*Endpoint not found*\n');
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/public/guides/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
- [Исходящие вебхуки, Обзор](https://dev.pachca.com/guides/webhook/overview.md): Исходящие вебхуки в Пачке: что это, как настроить и какие настройки доступны на вкладке Исходящий Webhook в боте
- [Исходящие вебхуки, Настройка и типы событий](https://dev.pachca.com/guides/webhook/events.md): Настройки исходящих вебхуков Пачки и список доступных типов событий: сообщения, реакции, нажатия кнопок, заполнение форм, изменение участников чатов и пространства, отправка ссылок
- [Исходящие вебхуки, Безопасность и обработчик](https://dev.pachca.com/guides/webhook/handler.md): Безопасность исходящих вебхуков Пачки: подпись HMAC-SHA256, проверка timestamp, IP-адрес отправителя, примеры обработчика на TypeScript и Python, идемпотентная обработка и доставка
- [Исходящие вебхуки, Поллинг](https://dev.pachca.com/guides/webhook/polling.md): Поллинг исходящих вебхуков Пачки через API: получение накопленных событий, удаление обработанных, пример кода для локальной разработки и сред с жёсткими firewall-правилами
- [Исходящие вебхуки, Поллинг](https://dev.pachca.com/guides/webhook/polling.md): Поллинг исходящих вебхуков Пачки через SDK: получение новых событий без публичного webhook URL, дедупликация доставок и пример воркера для локальной разработки
- [Кнопки в сообщениях](https://dev.pachca.com/guides/buttons.md): Интерактивные кнопки в сообщениях ботов Пачки: ссылки и действия, обработка нажатий через исходящий вебхук, открытие форм и переходы на внешние ресурсы
- [Формы, Обзор](https://dev.pachca.com/guides/forms/overview.md): Модальные формы ботов в Пачке: поля ввода, списки, даты и кнопки, жизненный цикл представления — от нажатия кнопки до валидации и закрытия модального окна
- [Формы, Блоки представления](https://dev.pachca.com/guides/forms/blocks.md): 10 типов блоков представлений в формах ботов Пачки: заголовок, текст, поля ввода, выбор из списка, дата, кнопки. До 100 блоков в одном представлении
Expand Down
Loading
Loading