Документация для разработчиков
Avigram API предоставляет уведомления в реальном времени при обнаружении новых объявлений на Avito. Система отправляет HTTP POST запросы на указанный endpoint со структурированными данными объявлений, позволяя интегрировать с вашими рабочими процессами и базами данных.
Обратите внимание: отправка данных через API доступна только на тарифах «Бизнес» и «Индивидуальный».
На тарифах «Базовый» и «Профессиональный» уведомления о новых объявлениях доставляются только в мессенджеры.
Content-Type: application/json
User-Agent: Avigram/1.0
X-Avigram-Timestamp: 1710000000000 # только если задан секретный ключ
X-Avigram-Signature: sha256=<hex> # только если задан секретный ключ
| Заголовок | Обязательный | Описание |
|---|---|---|
Content-Type |
Да | Всегда application/json |
User-Agent |
Да | Всегда Avigram/1.0 |
X-Avigram-Timestamp |
Нет | Unix timestamp в миллисекундах. Присутствует только при заданном секретном ключе |
X-Avigram-Signature |
Нет | HMAC-подпись тела. Присутствует только при заданном секретном ключе. Формат: sha256=<hex> |
В настройках ссылки можно указать секретный ключ (callback_secret). Это необязательно.
2xx.X-Avigram-Timestamp и X-Avigram-Signature. На своей стороне вы можете (и рекомендуется) проверить подпись, чтобы убедиться, что запрос пришёл от Avigram, а не от постороннего.Коротко: секретный ключ — опциональная защита. Без него интеграция работает как раньше. С ключом вы получаете HMAC-подпись тела запроса.
signature = HMAC-SHA256(secret, timestamp + "." + rawBody)
# заголовок: X-Avigram-Signature: sha256=<hex-digest>
secret — ваш секретный ключ из настроек ссылкиtimestamp — значение заголовка X-Avigram-Timestamp (строка)rawBody — сырое тело HTTP-запроса (байты/строка JSON как пришло), без повторного JSON.stringifyinterface AvitoItem {
id: number;
title: string;
price: string | null;
address: string | null;
description: string | null;
time: string;
timestamp: number;
link: string;
markView: string | null;
delivery: string | null;
parameters: Parameter[];
seller: SellerInfo;
images: string[];
info: SearchInfo;
}
interface Parameter {
title: string;
description: string;
}
interface SearchInfo {
searchId: number;
searchName: string;
searchUrl: string;
userId: number;
sentAt: number;
subExpiresAt: number | null;
}
interface SellerInfo {
id: string | null;
name: string;
type: "Частное лицо" | "Компания";
rating: number | null;
reviews: string | null;
summary: string | null;
registration: string | null;
}
| Поле | Тип | Nullable | Описание |
|---|---|---|---|
id |
number | Нет | Уникальный ID объявления Avito |
title |
string | Нет | Заголовок объявления |
price |
string | Да | Цена с валютой |
address |
string | Да | Полный адрес (город + улица) |
description |
string | Да | Описание объявления (может быть пустым) |
time |
string | Нет | Время публикации в человекочитаемом формате по МСК |
timestamp |
number | Нет | Unix timestamp в миллисекундах |
link |
string | Нет | Прямая ссылка на объявление Avito |
markView |
string | Да | Маркетинговый бейдж (Рыночная цена и т.д) или null |
delivery |
string | Да | Информация о доставке или null |
parameters |
Parameter[] | Нет | Характеристики объявления (состояние, бренд, память и т.д.); пустой массив, если параметров нет |
parameters[].title |
string | Нет | Название параметра (например: «Состояние», «Бренд») |
parameters[].description |
string | Нет | Значение параметра (например: «Б/у», «Apple») |
seller.id |
string | Да | Внутренний ID продавца |
seller.name |
string | Нет | Имя продавца |
seller.type |
string | Нет | Тип продавца |
seller.rating |
number | Да | Рейтинг продавца (0.0-5.0) |
seller.reviews |
string | Да | Текст с количеством отзывов |
seller.summary |
string | Да | Дополнительная информация о продавце |
seller.registration |
string | Да | Дата регистрации |
images |
string[] | Нет | Массив URL изображений (максимум 5) |
info.searchId |
number | Нет | ID поиска/ссылки в Avigram |
info.searchName |
string | Нет | Название поиска (в Avigram), по которому найдено объявление |
info.searchUrl |
string | Нет | URL поиска (на Авито), по которому найдено объявление |
info.userId |
number | Нет | ID пользователя в Avigram |
info.sentAt |
number | Нет | Unix timestamp в миллисекундах — момент отправки callback |
info.subExpiresAt |
number | null | Да | Unix timestamp в миллисекундах — дата истечения подписки пользователя |
Ниже — два сценария: без проверки (секрет не задан) и с проверкой HMAC (секрет задан в настройках ссылки).
const express = require('express');
const app = express();
app.use(express.json());
app.post('/avigram-callback', async (req, res) => {
try {
const item = req.body;
if (!item.id || !item.title) {
return res.status(400).json({ error: 'Invalid payload' });
}
await processListing(item);
res.status(200).send('OK');
} catch (error) {
console.error('Ошибка обработки avigram callback:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
async function processListing(item) {
console.log(`Новое объявление: ${item.title} - ${item.price}`);
console.log(`Поиск #${item.info.searchId} пользователя #${item.info.userId}`);
await saveListingToDatabase(item);
}
const express = require('express');
const crypto = require('crypto');
const app = express();
// Важно: для подписи нужен сырой body
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
}
}));
const SECRET = process.env.AVIGRAM_CALLBACK_SECRET; // тот же ключ, что в настройках ссылки
const MAX_SKEW_MS = 5 * 60 * 1000; // 5 минут
function verifyAvigramSignature(req) {
const timestamp = req.headers['x-avigram-timestamp'];
const signature = req.headers['x-avigram-signature'];
if (!timestamp || !signature) {
return false;
}
const ts = Number(timestamp);
if (!Number.isFinite(ts) || Math.abs(Date.now() - ts) > MAX_SKEW_MS) {
return false;
}
const expected = 'sha256=' + crypto
.createHmac('sha256', SECRET)
.update(`${timestamp}.${req.rawBody}`)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
} catch {
return false;
}
}
app.post('/avigram-callback', async (req, res) => {
if (!verifyAvigramSignature(req)) {
return res.status(401).json({ error: 'Invalid signature' });
}
try {
const item = req.body;
if (!item.id || !item.title) {
return res.status(400).json({ error: 'Invalid payload' });
}
await processListing(item);
res.status(200).send('OK');
} catch (error) {
console.error('Ошибка обработки avigram callback:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
async function processListing(item) {
// ваша логика
}
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
app = FastAPI()
class Parameter(BaseModel):
title: str
description: str
class SellerInfo(BaseModel):
id: Optional[str]
name: str
type: str
rating: Optional[float]
reviews: Optional[str]
summary: Optional[str]
registration: Optional[str]
class SearchInfo(BaseModel):
searchId: int
searchName: str
searchUrl: str
userId: int
sentAt: int
subExpiresAt: Optional[int]
class AvitoItem(BaseModel):
id: int
title: str
price: Optional[str]
address: Optional[str]
description: Optional[str]
time: str
timestamp: int
link: str
markView: Optional[str]
delivery: Optional[str]
parameters: List[Parameter]
seller: SellerInfo
images: List[str]
info: SearchInfo
@app.post("/avigram-callback")
async def receive_callback(item: AvitoItem):
try:
await process_listing(item)
return {"status": "success"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
async def process_listing(item: AvitoItem):
# Ваша реализация здесь
pass
import hmac
import hashlib
import os
import time
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
SECRET = os.environ["AVIGRAM_CALLBACK_SECRET"]
MAX_SKEW_MS = 5 * 60 * 1000
def verify_signature(raw_body: bytes, timestamp: str | None, signature: str | None) -> bool:
if not timestamp or not signature:
return False
try:
ts = int(timestamp)
except ValueError:
return False
if abs(int(time.time() * 1000) - ts) > MAX_SKEW_MS:
return False
expected = "sha256=" + hmac.new(
SECRET.encode("utf-8"),
f"{timestamp}.".encode("utf-8") + raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(signature, expected)
@app.post("/avigram-callback")
async def receive_callback(request: Request):
raw_body = await request.body()
ok = verify_signature(
raw_body,
request.headers.get("x-avigram-timestamp"),
request.headers.get("x-avigram-signature"),
)
if not ok:
raise HTTPException(status_code=401, detail="Invalid signature")
item = await request.json()
# обработка item ...
return {"status": "success"}
use Illuminate\Http\Request;
Route::post('/avigram-callback', function (Request $request) {
try {
$data = $request->validate([
'id' => 'required|integer',
'title' => 'required|string',
'price' => 'nullable|string',
'address' => 'nullable|string',
'info' => 'required|array',
'info.searchId' => 'required|integer',
'info.searchName' => 'required|string',
'info.searchUrl' => 'required|string',
'info.userId' => 'required|integer',
'info.sentAt' => 'required|integer',
]);
processAvitoListing($data);
return response()->json(['status' => 'success'], 200);
} catch (\Exception $e) {
\Log::error('Ошибка avigram callback: ' . $e->getMessage());
return response()->json(['error' => 'Processing failed'], 500);
}
});
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
Route::post('/avigram-callback', function (Request $request) {
$secret = env('AVIGRAM_CALLBACK_SECRET');
$timestamp = $request->header('X-Avigram-Timestamp');
$signature = $request->header('X-Avigram-Signature');
$rawBody = $request->getContent();
if (!$timestamp || !$signature) {
return response()->json(['error' => 'Missing signature'], 401);
}
if (abs((int) (microtime(true) * 1000) - (int) $timestamp) > 5 * 60 * 1000) {
return response()->json(['error' => 'Stale request'], 401);
}
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
if (!hash_equals($expected, $signature)) {
return response()->json(['error' => 'Invalid signature'], 401);
}
try {
$data = $request->all();
processAvitoListing($data);
return response()->json(['status' => 'success'], 200);
} catch (\Exception $e) {
Log::error('Ошибка avigram callback: ' . $e->getMessage());
return response()->json(['error' => 'Processing failed'], 500);
}
});