-
Star
(132)
You must be signed in to star a gist -
Fork
(33)
You must be signed in to fork a gist
-
-
Save Mo45/cb0813cb8a6ebcd6524f6a36d4f8862c to your computer and use it in GitHub Desktop.
<?php | |
/** | |
* discord.msg.send.php v0.8 | |
* Kirill Krasin © 2025 | |
* https://github.com/Mo45 | |
* | |
* For revisions and comments vist: https://gist.github.com/Mo45/cb0813cb8a6ebcd6524f6a36d4f8862c | |
* | |
* Sends a message to Discord via Webhook with file support and rate limit handling | |
* | |
* This function handles both simple messages and complex embeds with attachments, | |
* automatically retrying on rate limits and validating file requirements. | |
* | |
* Create new webhook in your Discord channel settings and copy&paste URL into $webhookUrl. | |
* | |
* You can use Markdown in your message. More about formatting: https://discordapp.com/developers/docs/reference#message-formatting | |
* | |
* @param string $webhookUrl Full Discord webhook URL (from channel settings) | |
* @param array $payload Message data array (content, embeds, username, etc.) | |
* @param string|null $filePath Absolute path to file for attachment (optional) | |
* @param int $maxRetries Maximum retry attempts on rate limits (default: 3) | |
* @return bool True on success, false on failure (errors logged) | |
*/ | |
function sendDiscordWebhook(string $webhookUrl, array $payload, ?string $filePath = null, int $maxRetries = 3): bool | |
{ | |
// ================================================================= | |
// FILE VALIDATION CHECKS | |
// ================================================================= | |
// Verify file existence before processing | |
if ($filePath && !file_exists($filePath)) { | |
error_log("File not found: $filePath"); | |
return false; | |
} | |
// Enforce Discord's file size limit (8MB standard servers, 50MB boosted) | |
if ($filePath && filesize($filePath) > 8 * 1024 * 1024) { | |
error_log("File size exceeds Discord limit: $filePath"); | |
return false; | |
} | |
$retryCount = 0; | |
// ================================================================= | |
// RATE LIMIT RETRY LOOP | |
// ================================================================= | |
while ($retryCount <= $maxRetries) { | |
$ch = curl_init($webhookUrl); | |
// ============================================================= | |
// CURL BASE CONFIGURATION | |
// ============================================================= | |
$options = [ | |
CURLOPT_FOLLOWLOCATION => true, // Follow redirects (301/302) | |
CURLOPT_HEADER => true, // Include headers in response | |
CURLOPT_RETURNTRANSFER => true, // Return transfer instead of output | |
CURLOPT_TIMEOUT => 120, // Maximum execution time (seconds) | |
CURLOPT_SSL_VERIFYPEER => true, // Verify SSL certificate | |
CURLOPT_POST => true, // Always use POST method | |
]; | |
// ============================================================= | |
// PAYLOAD PREPARATION: FILE ATTACHMENT MODE | |
// ============================================================= | |
if ($filePath) { | |
// Generate unique boundary for multipart separation | |
$boundary = '----DiscordWebhookBoundary' . uniqid(); | |
// Encode metadata payload to JSON | |
$payloadJson = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); | |
// ========================================================= | |
// MULTIPART REQUEST BODY CONSTRUCTION | |
// ========================================================= | |
// Part 1: JSON metadata payload | |
$body = "--$boundary\r\n"; | |
$body .= "Content-Disposition: form-data; name=\"payload_json\"\r\n"; | |
$body .= "Content-Type: application/json\r\n\r\n"; | |
$body .= $payloadJson . "\r\n"; | |
// Part 2: File attachment | |
$body .= "--$boundary\r\n"; | |
$body .= "Content-Disposition: form-data; name=\"file\"; filename=\"" . basename($filePath) . "\"\r\n"; | |
$body .= "Content-Type: " . mime_content_type($filePath) . "\r\n\r\n"; | |
$body .= file_get_contents($filePath) . "\r\n"; | |
// End boundary marker | |
$body .= "--$boundary--\r\n"; | |
// ========================================================= | |
// CURL CONFIG FOR FILE UPLOAD | |
// ========================================================= | |
$options[CURLOPT_HTTPHEADER] = [ | |
"Content-Type: multipart/form-data; boundary=$boundary", | |
"Content-Length: " . strlen($body) | |
]; | |
$options[CURLOPT_POSTFIELDS] = $body; | |
} | |
// ============================================================= | |
// PAYLOAD PREPARATION: JSON-ONLY MODE | |
// ============================================================= | |
else { | |
// Standard JSON request configuration | |
$options[CURLOPT_HTTPHEADER] = ['Content-Type: application/json']; | |
$options[CURLOPT_POSTFIELDS] = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); | |
} | |
// Apply all configured options | |
curl_setopt_array($ch, $options); | |
// ============================================================= | |
// EXECUTE CURL REQUEST | |
// ============================================================= | |
$response = curl_exec($ch); | |
// ============================================================= | |
// CURL ERROR HANDLING | |
// ============================================================= | |
if ($response === false) { | |
$error = curl_error($ch); | |
$errno = curl_errno($ch); | |
curl_close($ch); | |
error_log("cURL error ($errno): $error"); | |
return false; | |
} | |
// ============================================================= | |
// RESPONSE PROCESSING | |
// ============================================================= | |
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); | |
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); | |
$responseHeaders = substr($response, 0, $headerSize); | |
$responseBody = substr($response, $headerSize); | |
curl_close($ch); | |
// ============================================================= | |
// SUCCESSFUL DELIVERY (2xx STATUS) | |
// ============================================================= | |
if ($httpCode >= 200 && $httpCode < 300) { | |
return true; | |
} | |
// ============================================================= | |
// RATE LIMIT HANDLING (429 TOO MANY REQUESTS) | |
// ============================================================= | |
if ($httpCode === 429) { | |
$retryAfter = 1; // Default cooldown (in seconds) | |
// Parse Retry-After header (seconds to wait) | |
if (preg_match('/retry-after: (\d+)/i', $responseHeaders, $matches)) { | |
$retryAfter = (int)$matches[1]; | |
} | |
// Fallback to Discord's rate-limit header | |
elseif (preg_match('/x-ratelimit-reset-after: (\d+\.?\d*)/i', $responseHeaders, $matches)) { | |
$retryAfter = (float)$matches[1]; | |
} | |
error_log("Rate limit hit. Retrying after {$retryAfter} seconds"); | |
sleep(ceil($retryAfter) + 1); // Add safety buffer | |
$retryCount++; | |
continue; | |
} | |
// ============================================================= | |
// ERROR LOGGING AND ANALYSIS | |
// ============================================================= | |
$errorInfo = [ | |
'http_code' => $httpCode, | |
'response' => $responseBody, | |
'headers' => $responseHeaders, | |
'file' => $filePath ? basename($filePath) : 'none' | |
]; | |
error_log("Discord API error: " . print_r($errorInfo, true)); | |
// ============================================================= | |
// FATAL ERROR HANDLING (4xx CLIENT ERRORS) | |
// ============================================================= | |
// Don't retry on permanent client errors (except 429) | |
if ($httpCode >= 400 && $httpCode < 500 && $httpCode !== 429) { | |
return false; | |
} | |
$retryCount++; | |
} | |
// ================================================================= | |
// FINAL FAILURE AFTER RETRIES | |
// ================================================================= | |
error_log("Failed after $maxRetries attempts"); | |
return false; | |
} | |
// ===================================================================== | |
// PASTE YOUR WEBHOOK URL HERE | |
// ===================================================================== | |
$webhookUrl = "YOUR_WEBHOOK_URL"; | |
// ===================================================================== | |
// MESSAGE CONFIGURATION | |
// ===================================================================== | |
// Base message parameters | |
$message = [ | |
// Text content | |
'content' => "This is message with mention, use userID <@12341234123412341>", | |
// Custom username | |
'username' => "php-msg-bot", | |
// Text-to-speech | |
'tts' => false, | |
// Mention permissions | |
'allowed_mentions' => [ | |
'parse' => ['users'] // Allow @user mentions | |
] | |
]; | |
// ===================================================================== | |
// EMBED CONSTRUCTION | |
// ===================================================================== | |
$embed = [ | |
'title' => "Annotated Discord Webhook", // Embed title | |
'type' => "rich", // Embed type | |
'description' => "Embed with detailed documentation", // Main text | |
'url' => "https://gist.github.com/Mo45/cb0813cb8a6ebcd6524f6a36d4f8862c", //URL of title link | |
'timestamp' => date(DATE_ATOM), // ISO 8601 timestamp | |
'color' => hexdec("3366ff"), // Left border color | |
'footer' => [ // Footer section | |
'text' => "GitHub.com/Mo45", | |
'icon_url' => "https://avatars.githubusercontent.com/u/4648775" | |
], | |
'author' => [ // Author section | |
'name' => "Mo45", | |
'url' => "https://github.com/Mo45" | |
], | |
'fields' => [ // Data fields | |
[ | |
'name' => "New Feature", | |
'value' => "File upload", | |
'inline' => true | |
], | |
[ | |
'name' => "Status", | |
'value' => "Completed", | |
'inline' => true | |
] | |
] | |
]; | |
// Attach embed to message | |
$message['embeds'] = [$embed]; | |
// ===================================================================== | |
// FILE ATTACHMENT (OPTIONAL) | |
// ===================================================================== | |
// Uncomment to enable file upload: | |
// $filePath = __DIR__ . '/test.jpg'; | |
// ===================================================================== | |
// EXECUTION | |
// ===================================================================== | |
$result = sendDiscordWebhook( | |
webhookUrl: $webhookUrl, | |
payload: $message, | |
filePath: $filePath ?? null, // Pass null if no file | |
maxRetries: 5 | |
); | |
if ($result) { | |
echo "Message delivered successfully!"; | |
} else { | |
echo "Delivery failed. Check error logs for details."; | |
} |
Can you not send discord webhook requests with 000webhost? I wrote my own code and it didn't work so I came to this and this doesn't work either. It just keeps returning the webhook data you would get with a GET request, but it's not sending any data.
Just to clarify, this code works as it should work.
I'm only wondering is it possible to send reactions with this?
Just to clarify, this code works as it should work.
I'm only wondering is it possible to send reactions with this?
Works flawlessly. Thanks a lot!
Hey i have a problem, my code run in linux but not in my IIS website.
when i display $response on my IIS server it returns false. I don't know why. My two servers are in https.
EDIT: I have var_dump($json_date) is everything is fine I do not understand
Thanks
@nschwaller most likely you need to install curl extension. Read your server error logs.
i have curl in my iis server...
2021-04-15 12:19:51 152.228.218.73 GET /neverland/ - 443 - (my ip serv) Mozilla/5.0+(compatible;+Discordbot/2.0;++https://discordapp.com/) - 200 0 0 93
edit: mb this my error :
[15-Apr-2021 13:00:57 UTC] PHP Warning: PHP Startup: Unable to load dynamic library 'php_xmlrpc.dll' (tried: C:\Program Files\PHP\v8.0\ext\php_xmlrpc.dll (The specified module could not be found), C:\Program Files\PHP\v8.0\ext\php_php_xmlrpc.dll.dll (The specified module could not be found)) in Unknown on line 0
You might not have it installed.
https://www.php.net/manual/en/xmlrpc.installation.php
Works brilliantly. Is it possible to send a direct message to a user using a variant of this? (Not mentioning a user, but actually private messaging them)
You would need to use a discord bot to do that. (see discord.php)
You would need to use a discord bot to do that. (see discord.php)
Apologies if it is blatantly obvious, but where is the discord.php?
You would need to use a discord bot to do that. (see discord.php)
Apologies if it is blatantly obvious, but where is the discord.php?
@Mo45 thank you for this, it works flawlessly! Although I couldn't make it work for .svg images, any clue?
SVG images don't render on discord
how can i edit or delete a message sent by webhook?
Hello. This is isn't Discord API support forum =)
1min Googling give me this thread, maybe it will help.
This code is a 1 in a million jackpot its work insane but I'm trying to learn how to add it to my form submit button can you help me by any chance, please. thanks
@Mo45
thank you for this code.
i want ask , if it's possible to send a PDF as attachment ?
Yep, Discord accepts most if not all file types.
Did you ever figure out how to make it work on submit?
Did you ever figure out how to make it work on submit?
you can using this endpoint API : /channels/ {channel_id {/messages}
I just created a package for this.
https://packagist.org/packages/atakde/discord-webhook-php
https://github.com/atakde/discord-webhook-php
I just created a package for this. https://packagist.org/packages/atakde/discord-webhook-php https://github.com/atakde/discord-webhook-php
How do you do embeds? Your documentation does not cover them.
I just created a package for this. https://packagist.org/packages/atakde/discord-webhook-php https://github.com/atakde/discord-webhook-php
How do you do embeds? Your documentation does not cover them.
You are right, actually we don't have documentation. I will prepare :)
You can use like that;
$messageFactory = new MessageFactory();
$message= $messageFactory->create('embed');
https://github.com/atakde/discord-webhook-php/blob/master/src/Message/EmbedMessage.php
I just created a package for this. https://packagist.org/packages/atakde/discord-webhook-php https://github.com/atakde/discord-webhook-php
How do you do embeds? Your documentation does not cover them.
You are right, actually we don't have documentation. I will prepare :)
You can use like that;
$messageFactory = new MessageFactory(); $message= $messageFactory->create('embed');
https://github.com/atakde/discord-webhook-php/blob/master/src/Message/EmbedMessage.php
I tried to use it with fields and it just wouldn't work. I ended up going with my own solution.
I just created a package for this. https://packagist.org/packages/atakde/discord-webhook-php https://github.com/atakde/discord-webhook-php
How do you do embeds? Your documentation does not cover them.
You are right, actually we don't have documentation. I will prepare :)
You can use like that;$messageFactory = new MessageFactory(); $message= $messageFactory->create('embed');
https://github.com/atakde/discord-webhook-php/blob/master/src/Message/EmbedMessage.php
I tried to use it with fields and it just wouldn't work. I ended up going with my own solution.
$embedMessage = $messageFactory->create('embed');
$embedMessage->setTitle("Title");
$embedMessage->setAuthorUrl("https://doodleipsum.com/700?i=f8b1abea359b643310916a38aa0b0562");
$embedMessage->setAuthorIcon("https://doodleipsum.com/700?i=f8b1abea359b643310916a38aa0b0562");
$embedMessage->setFields([
[
'name' => 'Field 1',
'value' => 'Value 1',
'inline' => true
],
[
'name' => 'Field 2',
'value' => 'Value 2',
'inline' => false
]
]);
$webhook = new DiscordWebhook($embedMessage);
$webhook->setWebhookUrl("YOUR_WEBHOOK_URL");;
$res = $webhook->send();
I tested in my end and it is working. Feel free to mail me the issue. This is the example that I tested.
works well thanks <3
can confirm this works , thanks
Updated code. Key features in this version (v0.8):
- File Validation
- Checks file existence before processing & enforces Discord's 8MB file size limit
- Rate Limit Handling
- Automatic retry loop with configurable attempts & Discord's Retry-After header parsing
- Multipart Request
- Proper CRLF line endings (RFC-compliant)
- Combines JSON metadata + binary file
- And other cool stuff like SSL verification, error handling, ISO 8601 timestamp
Find this code useful? Feel free to donate.
Hmm still doesnt seem to work. And I apologize, I don't use php and I'm just trying to get this to work so I can move on.
I tried hosting it too on Awardspace.com but with no luck. Any clue as to why this is occuring?
(I copied the entire code you sent and just replaced the url with my webhook)