Отправка Discord Webhook из PHP
Полное руководство по отправке Discord webhook из PHP. Примеры с cURL и Guzzle, создание эмбедов, загрузка файлов и интеграция с Laravel.
Discord webhook позволяет отправлять автоматические уведомления в каналы Discord из PHP-приложений. В этом руководстве мы рассмотрим все способы работы с webhook: от простых запросов через cURL до продвинутой интеграции с Laravel.
Основы Discord Webhook
Webhook в Discord — это специальный URL, который принимает POST-запросы с JSON-данными и публикует сообщения в канал. Это простой способ интеграции уведомлений без необходимости создавать полноценного бота.
Типичные сценарии использования:
- Уведомления об ошибках в приложении
- Алерты о новых заказах в интернет-магазине
- Отчёты о результатах деплоя
- Мониторинг серверов и сервисов
- Интеграция с формами обратной связи
- Отложенные сообщения по расписанию
- Опросы для сбора обратной связи
Отправка простого сообщения через cURL
Начнём с базового примера отправки текстового сообщения используя встроенные функции PHP.
<?php
function sendDiscordMessage($webhookUrl, $message) {
$data = json_encode([
'content' => $message
]);
$ch = curl_init($webhookUrl);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode >= 200 && $httpCode < 300;
}
// Использование
$webhookUrl = 'https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN';
$success = sendDiscordMessage($webhookUrl, 'Привет из PHP!');
if ($success) {
echo "Сообщение отправлено успешно";
} else {
echo "Ошибка отправки сообщения";
}
Создание класса для работы с webhook
Для удобства создадим класс, который инкапсулирует всю логику работы с Discord webhook.
<?php
class DiscordWebhook {
private $webhookUrl;
private $timeout = 10;
public function __construct($webhookUrl) {
$this->webhookUrl = $webhookUrl;
}
public function setTimeout($seconds) {
$this->timeout = $seconds;
return $this;
}
public function send($data) {
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$ch = curl_init($this->webhookUrl);
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['Content-Type: application/json; charset=utf-8'],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $json,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_TIMEOUT => $this->timeout
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
throw new Exception("cURL error: $error");
}
if ($httpCode === 429) {
throw new Exception("Rate limit exceeded");
}
if ($httpCode < 200 || $httpCode >= 300) {
throw new Exception("HTTP error $httpCode: $response");
}
return true;
}
public function sendMessage($content, $username = null, $avatarUrl = null) {
$data = ['content' => $content];
if ($username) {
$data['username'] = $username;
}
if ($avatarUrl) {
$data['avatar_url'] = $avatarUrl;
}
return $this->send($data);
}
}
Использование класса:
$webhook = new DiscordWebhook('https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN');
try {
$webhook->sendMessage(
'Новый заказ на сумму 5000 руб.',
'Магазин Bot',
'https://example.com/shop-icon.png'
);
echo "Уведомление отправлено";
} catch (Exception $e) {
error_log("Discord webhook error: " . $e->getMessage());
}
Отправка эмбедов
Эмбеды позволяют создавать структурированные и красиво оформленные сообщения. Расширим наш класс для поддержки эмбедов.
class DiscordEmbed {
private $data = [];
public function setTitle($title) {
$this->data['title'] = $title;
return $this;
}
public function setDescription($description) {
$this->data['description'] = $description;
return $this;
}
public function setColor($color) {
// Принимает hex (#FF5733) или decimal (16734003)
if (is_string($color) && strpos($color, '#') === 0) {
$color = hexdec(substr($color, 1));
}
$this->data['color'] = $color;
return $this;
}
public function setUrl($url) {
$this->data['url'] = $url;
return $this;
}
public function setTimestamp($timestamp = null) {
$this->data['timestamp'] = $timestamp ?? date('c');
return $this;
}
public function setFooter($text, $iconUrl = null) {
$this->data['footer'] = ['text' => $text];
if ($iconUrl) {
$this->data['footer']['icon_url'] = $iconUrl;
}
return $this;
}
public function setThumbnail($url) {
$this->data['thumbnail'] = ['url' => $url];
return $this;
}
public function setImage($url) {
$this->data['image'] = ['url' => $url];
return $this;
}
public function setAuthor($name, $url = null, $iconUrl = null) {
$this->data['author'] = ['name' => $name];
if ($url) {
$this->data['author']['url'] = $url;
}
if ($iconUrl) {
$this->data['author']['icon_url'] = $iconUrl;
}
return $this;
}
public function addField($name, $value, $inline = false) {
if (!isset($this->data['fields'])) {
$this->data['fields'] = [];
}
$this->data['fields'][] = [
'name' => $name,
'value' => $value,
'inline' => $inline
];
return $this;
}
public function toArray() {
return $this->data;
}
}
Добавим метод в класс DiscordWebhook:
public function sendEmbed($embed, $content = null) {
$data = [];
if ($content) {
$data['content'] = $content;
}
if ($embed instanceof DiscordEmbed) {
$data['embeds'] = [$embed->toArray()];
} elseif (is_array($embed)) {
$data['embeds'] = array_map(function($e) {
return $e instanceof DiscordEmbed ? $e->toArray() : $e;
}, $embed);
}
return $this->send($data);
}
Пример использования для уведомления об ошибке:
$webhook = new DiscordWebhook('https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN');
$embed = (new DiscordEmbed())
->setTitle('Критическая ошибка')
->setDescription('Обнаружена ошибка в модуле оплаты')
->setColor('#FF0000')
->addField('Файл', 'payment.php', true)
->addField('Строка', '142', true)
->addField('Сообщение', 'Division by zero', false)
->setFooter('Система мониторинга')
->setTimestamp();
try {
$webhook->sendEmbed($embed, '@here Требуется внимание!');
} catch (Exception $e) {
error_log($e->getMessage());
}
Подробнее о цветах эмбедов читайте в статье Цвета Discord Embed.
Отправка файлов
Discord webhook поддерживает загрузку файлов. Для этого используется multipart/form-data.
public function sendFile($filePath, $message = null, $filename = null) {
if (!file_exists($filePath)) {
throw new Exception("File not found: $filePath");
}
$filename = $filename ?? basename($filePath);
$boundary = uniqid();
$delimiter = '-------------' . $boundary;
$postData = '';
// Добавляем JSON payload если есть сообщение
if ($message) {
$postData .= "--$delimiter\r\n";
$postData .= 'Content-Disposition: form-data; name="payload_json"' . "\r\n";
$postData .= "Content-Type: application/json\r\n\r\n";
$postData .= json_encode(['content' => $message]) . "\r\n";
}
// Добавляем файл
$fileContents = file_get_contents($filePath);
$postData .= "--$delimiter\r\n";
$postData .= 'Content-Disposition: form-data; name="file"; filename="' . $filename . '"' . "\r\n";
$postData .= "Content-Type: application/octet-stream\r\n\r\n";
$postData .= $fileContents . "\r\n";
$postData .= "--$delimiter--\r\n";
$ch = curl_init($this->webhookUrl);
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
"Content-Type: multipart/form-data; boundary=$delimiter",
"Content-Length: " . strlen($postData)
],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postData,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_TIMEOUT => $this->timeout
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300) {
throw new Exception("HTTP error $httpCode: $response");
}
return true;
}
Использование:
$webhook->sendFile(
'/var/log/app-errors.log',
'Лог-файл с ошибками за сегодня',
'errors-' . date('Y-m-d') . '.log'
);
Использование Guzzle HTTP Client
Для более продвинутых сценариев рекомендуется использовать Guzzle — популярный HTTP-клиент для PHP.
Установка через Composer:
composer require guzzlehttp/guzzle
Класс с использованием Guzzle:
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
class GuzzleDiscordWebhook {
private $client;
private $webhookUrl;
public function __construct($webhookUrl) {
$this->webhookUrl = $webhookUrl;
$this->client = new Client([
'timeout' => 10,
'verify' => true
]);
}
public function send($data) {
try {
$response = $this->client->post($this->webhookUrl, [
'json' => $data,
'headers' => [
'Content-Type' => 'application/json'
]
]);
return $response->getStatusCode() >= 200 && $response->getStatusCode() < 300;
} catch (GuzzleException $e) {
throw new Exception("Webhook error: " . $e->getMessage());
}
}
public function sendMessage($content, $username = null) {
$data = ['content' => $content];
if ($username) {
$data['username'] = $username;
}
return $this->send($data);
}
public function sendEmbed($embed) {
return $this->send([
'embeds' => [$embed instanceof DiscordEmbed ? $embed->toArray() : $embed]
]);
}
public function sendFile($filePath, $message = null) {
$multipart = [
[
'name' => 'file',
'contents' => fopen($filePath, 'r'),
'filename' => basename($filePath)
]
];
if ($message) {
$multipart[] = [
'name' => 'payload_json',
'contents' => json_encode(['content' => $message])
];
}
try {
$response = $this->client->post($this->webhookUrl, [
'multipart' => $multipart
]);
return $response->getStatusCode() >= 200 && $response->getStatusCode() < 300;
} catch (GuzzleException $e) {
throw new Exception("File upload error: " . $e->getMessage());
}
}
}
Интеграция с Laravel
Для Laravel-приложений создадим сервис-провайдер и фасад для удобной работы с webhook.
Создание сервиса
Создайте файл app/Services/DiscordWebhookService.php:
<?php
namespace App\Services;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Log;
class DiscordWebhookService {
protected $client;
protected $webhookUrl;
public function __construct() {
$this->webhookUrl = config('services.discord.webhook_url');
$this->client = new Client(['timeout' => 10]);
}
public function notify($message, $level = 'info') {
$colors = [
'info' => 3447003, // Синий
'success' => 3066993, // Зелёный
'warning' => 16776960, // Жёлтый
'error' => 15158332 // Красный
];
$embed = [
'title' => ucfirst($level),
'description' => $message,
'color' => $colors[$level] ?? $colors['info'],
'timestamp' => now()->toIso8601String(),
'footer' => [
'text' => config('app.name')
]
];
return $this->send(['embeds' => [$embed]]);
}
public function error($exception, $context = []) {
$embed = [
'title' => 'Application Error',
'description' => $exception->getMessage(),
'color' => 15158332,
'fields' => [
[
'name' => 'Exception',
'value' => get_class($exception),
'inline' => true
],
[
'name' => 'File',
'value' => $exception->getFile() . ':' . $exception->getLine(),
'inline' => false
]
],
'timestamp' => now()->toIso8601String()
];
if (!empty($context)) {
$embed['fields'][] = [
'name' => 'Context',
'value' => '```json\n' . json_encode($context, JSON_PRETTY_PRINT) . '\n```',
'inline' => false
];
}
return $this->send([
'content' => '@here Critical error occurred!',
'embeds' => [$embed]
]);
}
protected function send($data) {
try {
$response = $this->client->post($this->webhookUrl, [
'json' => $data
]);
return $response->getStatusCode() >= 200 && $response->getStatusCode() < 300;
} catch (\Exception $e) {
Log::error('Discord webhook failed: ' . $e->getMessage());
return false;
}
}
}
Конфигурация
Добавьте в config/services.php:
'discord' => [
'webhook_url' => env('DISCORD_WEBHOOK_URL'),
'error_webhook_url' => env('DISCORD_ERROR_WEBHOOK_URL'),
],
В .env:
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN
DISCORD_ERROR_WEBHOOK_URL=https://discord.com/api/webhooks/ERROR_ID/ERROR_TOKEN
Регистрация в Service Provider
В app/Providers/AppServiceProvider.php:
use App\Services\DiscordWebhookService;
public function register() {
$this->app->singleton(DiscordWebhookService::class, function ($app) {
return new DiscordWebhookService();
});
}
Использование в контроллерах
<?php
namespace App\Http\Controllers;
use App\Services\DiscordWebhookService;
use Illuminate\Http\Request;
class OrderController extends Controller {
protected $discord;
public function __construct(DiscordWebhookService $discord) {
$this->discord = $discord;
}
public function store(Request $request) {
$order = Order::create($request->all());
// Отправляем уведомление в Discord
$this->discord->notify(
"Новый заказ #{$order->id} на сумму {$order->total} руб.",
'success'
);
return response()->json($order, 201);
}
}
Обработчик исключений
В app/Exceptions/Handler.php:
use App\Services\DiscordWebhookService;
public function report(Throwable $exception) {
if ($this->shouldReport($exception)) {
$discord = app(DiscordWebhookService::class);
$discord->error($exception, [
'url' => request()->fullUrl(),
'user_id' => auth()->id(),
'ip' => request()->ip()
]);
}
parent::report($exception);
}
Обработка rate limits
Discord ограничивает количество запросов к webhook. Реализуем простую систему очередей для Laravel.
Создайте Job:
<?php
namespace App\Jobs;
use App\Services\DiscordWebhookService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SendDiscordNotification implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $message;
protected $level;
public function __construct($message, $level = 'info') {
$this->message = $message;
$this->level = $level;
}
public function handle(DiscordWebhookService $discord) {
$discord->notify($this->message, $this->level);
}
public function failed(\Throwable $exception) {
\Log::error('Discord notification failed: ' . $exception->getMessage());
}
}
Использование:
use App\Jobs\SendDiscordNotification;
SendDiscordNotification::dispatch('Новый заказ создан', 'success');
Практический пример: мониторинг формы обратной связи
Полный пример обработки формы с отправкой уведомления в Discord:
<?php
namespace App\Http\Controllers;
use App\Services\DiscordWebhookService;
use Illuminate\Http\Request;
class ContactController extends Controller {
public function submit(Request $request, DiscordWebhookService $discord) {
$validated = $request->validate([
'name' => 'required|max:255',
'email' => 'required|email',
'subject' => 'required|max:255',
'message' => 'required'
]);
// Сохраняем в БД
$contact = Contact::create($validated);
// Формируем красивое уведомление
$embed = [
'title' => 'Новое обращение',
'color' => 3447003,
'fields' => [
[
'name' => 'Имя',
'value' => $validated['name'],
'inline' => true
],
[
'name' => 'Email',
'value' => $validated['email'],
'inline' => true
],
[
'name' => 'Тема',
'value' => $validated['subject'],
'inline' => false
],
[
'name' => 'Сообщение',
'value' => mb_substr($validated['message'], 0, 1000),
'inline' => false
]
],
'footer' => [
'text' => 'ID: ' . $contact->id
],
'timestamp' => now()->toIso8601String()
];
$discord->send(['embeds' => [$embed]]);
return response()->json([
'message' => 'Спасибо за обращение!'
]);
}
}
Визуальный конструктор эмбедов
Создание сложных эмбедов вручную может быть трудоёмким. Используйте наш бесплатный визуальный конструктор для проектирования эмбедов и получения готового JSON-кода.
Больше информации о создании эмбедов в статье Руководство по Discord Embed Builder.
Лучшие практики
1. Валидация данных
Всегда проверяйте размеры полей перед отправкой:
class DiscordValidator {
public static function truncate($text, $maxLength) {
if (mb_strlen($text) <= $maxLength) {
return $text;
}
return mb_substr($text, 0, $maxLength - 3) . '...';
}
public static function validateEmbed($embed) {
$limits = [
'title' => 256,
'description' => 4096,
'fields' => 25,
'field_name' => 256,
'field_value' => 1024
];
if (isset($embed['title'])) {
$embed['title'] = self::truncate($embed['title'], $limits['title']);
}
if (isset($embed['description'])) {
$embed['description'] = self::truncate($embed['description'], $limits['description']);
}
if (isset($embed['fields'])) {
$embed['fields'] = array_slice($embed['fields'], 0, $limits['fields']);
foreach ($embed['fields'] as &$field) {
$field['name'] = self::truncate($field['name'], $limits['field_name']);
$field['value'] = self::truncate($field['value'], $limits['field_value']);
}
}
return $embed;
}
}
2. Безопасность webhook URL
Никогда не храните webhook URL в коде. Используйте переменные окружения и конфигурационные файлы.
3. Логирование ошибок
Всегда логируйте ошибки отправки webhook для отладки:
try {
$webhook->send($data);
} catch (Exception $e) {
error_log(sprintf(
'[Discord Webhook] Failed to send: %s in %s:%d',
$e->getMessage(),
$e->getFile(),
$e->getLine()
));
}
4. Асинхронная отправка
Для высоконагруженных приложений используйте очереди, чтобы не блокировать основной поток выполнения.
Заключение
Discord webhook в PHP — это простой и эффективный способ интеграции уведомлений в ваши приложения. Используя приведённые примеры, вы можете создать надёжную систему уведомлений для любых сценариев.
Основные выводы:
- Используйте классы для инкапсуляции логики
- Применяйте Guzzle для продвинутых сценариев
- Интегрируйте с Laravel через сервис-провайдеры
- Обрабатывайте ошибки и rate limits
- Валидируйте данные перед отправкой
Для более сложных workflow автоматизации изучите статью Автоматизация Discord Webhook.
Помимо кода, discord-webhook.com предлагает визуальный конструктор с такими функциями как отложенные сообщения, поддержка тредов и форумов, опросы и интерактивные кнопки с действиями — всё без написания кода.
Похожие статьи
- Руководство по Discord Embed Builder
- Цвета Discord Embed: полный справочник
- Настройка Discord Webhook — быстрый старт
- Отложенные сообщения Discord Webhook — Планирование отправки сообщений по расписанию из PHP
- Интерактивные кнопки и действия Discord — Добавление кнопок и действий в webhook-сообщения
Попробуйте в нашем инструменте
Открыть конструктор Discord Webhook