Skip to content

TypeError in send() method due to missing CURLOPT_RETURNTRANSFER #6

@dereuromark

Description

@dereuromark

Description

The send() method in DiscordWebhook.php throws a TypeError when Discord's API returns an error response (HTTP 400-599).

Error message:

TypeError: json_decode(): Argument #1 ($json) must be of type string, true given

Stack trace:

#0 vendor/atakde/discord-webhook-php/src/DiscordWebhook.php(79): json_decode()
#1 App\Queue\Task\ChatLogTask.php(40): Atakde\DiscordWebhook\DiscordWebhook->send()

Root Cause

The send() method does not set the CURLOPT_RETURNTRANSFER option before calling curl_exec().

Without this option:

  • curl_exec() returns true (boolean) on success instead of returning the response body as a string
  • When Discord returns an error response (e.g., 404, 400, 429), the code attempts to decode this boolean value on line 79
  • This causes a TypeError in PHP 8.0+

Current Code (Buggy)

public function send(): bool
{
    try {
        $ch = curl_init($this->webhookUrl);
        // is multipart/form-data
        if ($this->isMultipart()) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->message->toArray());
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: multipart/form-data']);
        } else {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->message->toJson());
            curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                'Content-Type: application/json',
                'Content-Length: ' . strlen($this->message->toJson())
            ));
        }

        $response = curl_exec($ch);  // Returns TRUE instead of response string!
        $responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($responseCode >= 200 && $responseCode < 300) {
            return true;
        } else {
            $decodedResponse = json_decode($response, true);  // TypeError: trying to decode TRUE
            throw new InvalidResponseException($decodedResponse["message"] ?? "Error ocurred!", $responseCode);
        }
    } catch (\Exception $e) {
        // suppress exception if debug is false
        if ($this->debug) {
            throw $e;
        }
        return false;
    }
}

Steps to Reproduce

  1. Set up a Discord webhook with an invalid URL or one that will return an error
  2. Attempt to send a message using the library
  3. When Discord returns an HTTP error (400-599), the TypeError occurs

Alternatively, to reproduce without Discord:

$messageFactory = new MessageFactory();
$textMessage = $messageFactory->create('text');
$textMessage->setUsername('Test-Bot');
$textMessage->setContent('Test message');

$webhook = new DiscordWebhook($textMessage);
$webhook->setWebhookUrl('https://discord.com/api/webhooks/invalid/webhook');  // Invalid URL
$webhook->send();  // Will throw TypeError when Discord returns 404

Expected Behavior

The method should properly handle error responses from Discord without throwing TypeErrors.

Proposed Fix

Add CURLOPT_RETURNTRANSFER option and handle empty/false responses:

public function send(): bool
{
    try {
        $ch = curl_init($this->webhookUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  // FIX: Add this line

        // is multipart/form-data
        if ($this->isMultipart()) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->message->toArray());
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: multipart/form-data']);
        } else {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->message->toJson());
            curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                'Content-Type: application/json',
                'Content-Length: ' . strlen($this->message->toJson())
            ));
        }

        $response = curl_exec($ch);
        $responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($responseCode >= 200 && $responseCode < 300) {
            return true;
        } else {
            // FIX: Handle false/empty responses gracefully
            $decodedResponse = json_decode($response ?: '{}', true);
            throw new InvalidResponseException($decodedResponse["message"] ?? "Error ocurred!", $responseCode);
        }
    } catch (\Exception $e) {
        // suppress exception if debug is false
        if ($this->debug) {
            throw $e;
        }
        return false;
    }
}

Environment

  • Library version: 2.0.0
  • PHP version: 8.3+ (but affects all PHP 8.x versions with type checking)
  • Operating System: Linux

Additional Notes

This bug has existed since the library was created but only manifests when Discord returns error responses. Many users may not have encountered it if their webhooks always succeed.

The fix is a simple two-line change:

  1. Add curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); after curl_init()
  2. Change json_decode($response, true) to json_decode($response ?: '{}', true)

Workaround

Until this is fixed, users can create a wrapper class that extends DiscordWebhook and overrides the send() method with the corrected implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions