WasenderApi - Low Cost WhatsApp API for Developers Processing Media Messages from Webhook Events - Help Center - WasenderApi - Low Cost WhatsApp API for Developers

Processing Media Messages from Webhook Events

Handling Media from Webhook Events

When receiving webhook events from WasenderApi, you may need to process media files such as images, videos, audio, or documents. This guide explains how to download and decrypt media files from the messages.upsert webhook event.

Important: Media files in WhatsApp are encrypted and require proper decryption before they can be used in your application. This article provides a step-by-step guide to handle this process securely.

Understanding Media in Webhook Events

When a message containing media is received, the messages.upsert webhook event will include information about the media, including:

  • mediaKey: The key required to decrypt the media file
  • url: The URL where the encrypted media can be downloaded
  • mimetype: The MIME type of the media (e.g., image/jpeg, video/mp4)
  • fileSha256: The SHA-256 hash of the file for verification
  • fileLength: The size of the file in bytes
  • mediaKeyTimestamp: The timestamp when the media key was generated

Here's an example of a webhook event containing media information:

{
  "event": "messages.upsert",
  "timestamp": 1633456789,
  "data": {
    "key": {
      "remoteJid": "1234567890@s.whatsapp.net",
      "fromMe": false,
      "id": "message-id-123"
    },
    "message": {
      "imageMessage": {
        "url": "https://mmg.whatsapp.net/o1/v/t62.7118-24/f2/m231/AQMBvwf5EiyXOdUQvtUDTcmS4ke_uYG1VJplhYBV8CejeAVhezKVUmB-cjS8kSl69SH0DeZss_i1c9h3ft18D5v7WncL7VF1BXO3zFwjGQ?ccb=9-4&oh=01_Q5Aa1gF5H2Hatef7zk77Pi86h_nQGkPchoxSCFh_amWPEp7vvg&oe=684B23A9&_nc_sid=e6ed6c&mms3=true",
        "mimetype": "image/jpeg",
        "caption": "Check out this image!",
        "fileSha256": "base64-encoded-sha256",
        "fileLength": "12345",
        "mediaKey": "SBqPa+ZHCoVLDdbSve+7sUbh+YDHyf+XoRuuvxdXj48=",
        "mediaKeyTimestamp": "1633456789"
      }
    }
  }
}

Decrypting WhatsApp Media

To use media files from WhatsApp, you need to download the encrypted file and then decrypt it using the provided media key. Below is a complete PHP implementation that handles this process:

<?php

/**
 * Decodes a base64url encoded string
 *
 * @param string $data The base64url encoded string
 * @return string The decoded data
 */
function base64url_decode($data)
{
    return base64_decode(strtr($data, '-_', '+/'));
}

/**
 * Downloads a file from a URL
 *
 * @param string $url The URL to download from
 * @return string|false The file contents or false on failure
 */
function downloadFile($url)
{
    $context = stream_context_create([
        "http" => [
            "follow_location" => true,
        ]
    ]);
    return file_get_contents($url, false, $context);
}

/**
 * Generates decryption keys for WhatsApp media
 *
 * @param string $mediaKey The media key from the webhook event
 * @param string $type The media type (image, video, audio, document)
 * @param int $length The length of the key material to generate
 * @return string The generated key material
 * @throws Exception If an invalid media type is provided
 */
function getDecryptionKeys(string $mediaKey, string $type = 'image', int $length = 112)
{
    $info = match ($type) {
        'image' => 'WhatsApp Image Keys',
        'video' => 'WhatsApp Video Keys',
        'audio' => 'WhatsApp Audio Keys',
        'document' => 'WhatsApp Document Keys',
        default => throw new Exception("Invalid media type"),
    };

    return hash_hkdf('sha256', base64_decode($mediaKey), $length, $info, '');
}

/**
 * Decrypts a WhatsApp media file
 *
 * @param string $mediaKey The media key from the webhook event
 * @param string $url The URL to download the encrypted file from
 * @param string $outputPath The path to save the decrypted file
 * @param string $mediaType The type of media (image, video, audio, document)
 * @throws Exception If download or decryption fails
 */
function decryptWhatsAppMedia(string $mediaKey, string $url, string $outputPath, string $mediaType = 'image')
{
    // Download the encrypted file
    $encFile = downloadFile($url);
    if (!$encFile) {
        throw new Exception("Failed to download file");
    }

    // Get decryption keys based on media type
    $keys = getDecryptionKeys($mediaKey, $mediaType);
    $iv = substr($keys, 0, 16);
    $cipherKey = substr($keys, 16, 32);

    // Remove the last 10 bytes (MAC) from the encrypted file
    $ciphertext = substr($encFile, 0, strlen($encFile) - 10);

    // Decrypt the file using AES-256-CBC
    $plaintext = openssl_decrypt($ciphertext, 'aes-256-cbc', $cipherKey, OPENSSL_RAW_DATA, $iv);
    if (!$plaintext) {
        throw new Exception("Failed to decrypt media");
    }

    // Save the decrypted file
    file_put_contents($outputPath, $plaintext);
    return true;
}

/**
 * Example usage in a webhook handler
 */
function handleMediaMessage($webhookData) {
    // Extract message data
    $message = $webhookData['data']['message'] ?? null;

    if (!$message) {
        return false;
    }

    // Determine the message type and extract media information
    if (isset($message['imageMessage'])) {
        $mediaInfo = $message['imageMessage'];
        $mediaType = 'image';
        $extension = '.jpg';
    } elseif (isset($message['videoMessage'])) {
        $mediaInfo = $message['videoMessage'];
        $mediaType = 'video';
        $extension = '.mp4';
    } elseif (isset($message['audioMessage'])) {
        $mediaInfo = $message['audioMessage'];
        $mediaType = 'audio';
        $extension = '.ogg';
    } elseif (isset($message['documentMessage'])) {
        $mediaInfo = $message['documentMessage'];
        $mediaType = 'document';
        $extension = ''; // Use the original extension if available
    } else {
        // Not a media message
        return false;
    }

    // Extract required information
    $mediaKey = $mediaInfo['mediaKey'] ?? null;
    $url = $mediaInfo['url'] ?? null;
    $messageId = $webhookData['data']['key']['id'] ?? 'unknown';

    if (!$mediaKey || !$url) {
        return false;
    }

    // Create a unique filename
    $outputPath = __DIR__ . '/media/' . $messageId . $extension;

    // Ensure the media directory exists
    if (!is_dir(__DIR__ . '/media/')) {
        mkdir(__DIR__ . '/media/', 0755, true);
    }

    try {
        // Decrypt and save the media file
        decryptWhatsAppMedia($mediaKey, $url, $outputPath, $mediaType);
        return $outputPath; // Return the path to the decrypted file
    } catch (Exception $e) {
        error_log("Media decryption error: " . $e->getMessage());
        return false;
    }
}

// Example of using this in a webhook endpoint
/*
app.post('/webhook', (req, res) => {
    // Verify webhook signature
    if (!verifySignature(req)) {
        return res.status(401).json({ error: 'Invalid signature' });
    }

    $webhookData = req.body;

    // Handle media messages
    if ($webhookData['event'] === 'messages.upsert') {
        $mediaPath = handleMediaMessage($webhookData);
        if ($mediaPath) {
            // Do something with the decrypted media file
            console.log('Media saved to: ' + $mediaPath);
        }
    }

    // Always respond with 200 OK
    res.status(200).json({ received: true });
});
*/
?>

Understanding the Code

Key Functions

  • base64url_decode(): Decodes base64url-encoded strings, which is a variant of base64 encoding that's safe for URLs.
  • downloadFile(): Downloads the encrypted media file from the provided URL.
  • getDecryptionKeys(): Generates the decryption keys using HKDF (HMAC-based Key Derivation Function) with the media key. Different media types use different info strings for key derivation.
  • decryptWhatsAppMedia(): The main function that downloads the encrypted file, derives the keys, and performs the decryption.
  • handleMediaMessage(): A helper function that extracts media information from the webhook data and handles the decryption process.

Decryption Process

  1. Download the encrypted file from the URL provided in the webhook event.
  2. Generate decryption keys using the media key and the appropriate info string based on media type.
  3. Extract the initialization vector (IV) and cipher key from the generated key material.
  4. Remove the last 10 bytes from the encrypted file, which is the MAC (Message Authentication Code).
  5. Decrypt the file using AES-256-CBC with the extracted cipher key and IV.
  6. Save the decrypted file to the specified output path.

Handling Different Media Types

The example code supports different types of media that can be received in WhatsApp messages:

  • Images: Usually in JPEG format, found in the imageMessage property.
  • Videos: Usually in MP4 format, found in the videoMessage property.
  • Audio: Usually in OGG format, found in the audioMessage property.
  • Documents: Any file type, found in the documentMessage property.

The handleMediaMessage() function automatically detects the media type and applies the appropriate decryption process.

Best Practices

  • Security: Always verify webhook signatures before processing media files to ensure they come from a trusted source.
  • Error Handling: Implement robust error handling to manage download failures, decryption errors, or invalid media types.
  • File Storage: Consider where and how long you store decrypted media files. Implement appropriate retention policies.
  • Performance: For high-volume applications, consider processing media asynchronously to avoid blocking your webhook endpoint.
  • Media Validation: After decryption, validate that the file is of the expected type and format before using it in your application.

Troubleshooting

Common Issues

  • Failed to download file: Ensure the URL is still valid and accessible. WhatsApp media URLs may expire after some time.
  • Failed to decrypt media: Verify that you're using the correct media key and media type. The decryption process is sensitive to these parameters.
  • Invalid media type: Make sure you're correctly identifying the media type from the webhook data structure.
  • Permission issues: Ensure your application has write permissions to the directory where you're saving the decrypted files.

Related Resources

  • Using Webhooks - Learn more about setting up and using webhooks with WasenderApi.
  • API Documentation - Complete documentation of all webhook event formats and API endpoints.

Still Need Help?

Can't find what you're looking for? Our support team is here to help.