Search code examples
phpdiscord

How to verify discord endpoint using php?


I am having some issues in creating a Discord bot. I want it to be able to respond to slash commands, but to do so I need to verify an endpoint. I am using PHP 7.4, and can't use any external libraries (hosting on a server that does not allow them). I have found documents for PHP, but they do require libraries to work. I tried taking the documents from Node.JS and "converting" them to PHP. Here's my code:

<?php

$public_key = "shh-it's-a-seekrit";
$headers = getallheaders();
$signature = $headers["X-Signature-Ed25519"];
$timestamp = $headers["X-Signature-Timestamp"];
$raw_body = file_get_contents('php://input');

/* To compute the signature, we need the following
 * 1. Message ($timestamp + $body)
 * 2. $signature
 * 3. $public_key
 * The algorythm is SHA-512
 */
$message = $timestamp . $raw_body;
$hash_signature = hash_hmac('sha512', $message, $public_key);
if (!hash_equals($signature, $hash_signature)) {
    header("HTTP/1.1 401 Unauthorized", true, 401);
    die("Request is not properly authorized!");
}
$return_array = [
    'type' => 1,
];
echo json_encode($return_array);
?>

When I put the address the file is uploaded to and try to save the changes, Discord says the following:

Validation errors: interactions_endpoint_url: The specified interactions endpoint URL could not be verified.


Solution

  • This is a method that works for me on PHP 8.1.

    Pass in the headers and raw JSON body, and it returns an array with the response code and payload to send back through whatever you are using to handle the response. Note the response must be JSON encoded.

    $discord_public is the public key from the Discord app.

    public function authorize(array $headers, string $body, string $discord_public): array
    {
        $res = [
            'code' => 200,
            'payload' => []
        ];
    
        if (!isset($headers['x-signature-ed25519']) || !isset($headers['x-signature-timestamp'])) {
            $res['code'] = 401;
            return $res;
        }
    
        $signature = $headers['x-signature-ed25519'];
        $timestamp = $headers['x-signature-timestamp'];
    
        if (!trim($signature, '0..9A..Fa..f') == '') {
            $res['code'] = 401;
            return $res;
        }
    
        $message = $timestamp . $body;
        $binary_signature = sodium_hex2bin($signature);
        $binary_key = sodium_hex2bin($discord_public);
    
        if (!sodium_crypto_sign_verify_detached($binary_signature, $message, $binary_key)) {
            $res['code'] = 401;
            return $res;
        }
    
        $payload = json_decode($body, true);
        switch ($payload['type']) {
            case 1:
                $res['payload']['type'] = 1;
                break;
    
            case 2:
                $res['payload']['type'] = 2;
                break;
    
            default:
                $res['code'] = 400;
                return $res;
        }
    
        return $res;
    }