Skip to main content
MadelineProto provides full support for Telegram’s secret chats, which offer end-to-end encryption with perfect forward secrecy. All messages, media, and files in secret chats are encrypted on the client side.

Secret Chat Overview

Secret chats offer:
  • End-to-end encryption - Messages encrypted on your device
  • Perfect forward secrecy - New encryption keys for each session
  • Self-destructing messages - Automatic message deletion (TTL)
  • No server storage - Messages not stored on Telegram servers
  • Screenshot notifications - Know when screenshots are taken
  • Device-specific - Chats tied to specific devices

Secret Chat Class

The SecretChat class represents an active secret chat:
use danog\MadelineProto\SecretChats\SecretChat;

// Secret chat properties
$chat->chatId;     // int - Secret chat ID
$chat->creator;    // bool - Whether we created this chat
$chat->otherID;    // int - User ID of other participant
$chat->created;    // int - Creation timestamp

Creating Secret Chats

Request a Secret Chat

use danog\MadelineProto\EventHandler\Attributes\Handler;
use danog\MadelineProto\EventHandler\Message\PrivateMessage;
use danog\MadelineProto\EventHandler\SimpleFilter\Incoming;

class SecretChatBot extends SimpleEventHandler
{
    #[Handler]
    public function handlePrivateMessage(Incoming&PrivateMessage $message): void
    {
        if ($message->message === 'request') {
            // Request a secret chat
            $this->requestSecretChat($message->senderId);
            
            $message->reply("Secret chat requested!");
        }
    }
}

Get Secret Chat Object

public function getSecretChatInfo(int $chatId): void
{
    $secretChat = $this->getSecretChat($chatId);
    
    $this->logger("Chat ID: {$secretChat->chatId}");
    $this->logger("Other user: {$secretChat->otherID}");
    $this->logger("Creator: " . ($secretChat->creator ? 'yes' : 'no'));
    $this->logger("Created: " . date('Y-m-d H:i:s', $secretChat->created));
}

Handling Secret Messages

Receive Secret Messages

use danog\MadelineProto\EventHandler\Message\SecretMessage;

#[Handler]
public function handleSecretMessage(Incoming&SecretMessage $message): void
{
    $this->logger("Secret message: {$message->message}");
    $this->logger("From chat: {$message->chatId}");
    $this->logger("Sender: {$message->senderId}");
    
    // Reply in secret chat
    $message->reply("Got your secret message!");
}

Send Secret Messages

public function sendSecretMessage(int $secretChatId): void
{
    // Send text message
    $this->sendMessage(
        peer: $secretChatId,
        message: "This is a secret message!"
    );
    
    // Send with TTL (self-destruct timer)
    $this->sendMessage(
        peer: $secretChatId,
        message: "This will self-destruct in 10 seconds",
        ttl: 10
    );
}

Sending Media in Secret Chats

Send Photos

use danog\MadelineProto\LocalFile;

public function sendSecretPhoto(int $secretChatId): void
{
    $this->sendPhoto(
        peer: $secretChatId,
        file: new LocalFile('photo.jpg'),
        caption: 'Secret photo',
        ttl: 10  // Self-destruct after 10 seconds
    );
}

Send Documents

public function sendSecretDocument(int $secretChatId): void
{
    $this->sendDocument(
        peer: $secretChatId,
        file: new LocalFile('document.pdf'),
        caption: 'Encrypted document',
        fileName: 'secret.pdf'
    );
}

Send Videos

public function sendSecretVideo(int $secretChatId): void
{
    $this->sendVideo(
        peer: $secretChatId,
        file: new LocalFile('video.mp4'),
        caption: 'Secret video',
        ttl: 15  // Auto-delete after viewing
    );
}

Send Audio

public function sendSecretAudio(int $secretChatId): void
{
    $this->sendAudio(
        peer: $secretChatId,
        file: new LocalFile('audio.mp3'),
        caption: 'Secret audio'
    );
}

Send Voice Messages

public function sendSecretVoice(int $secretChatId): void
{
    $this->sendVoice(
        peer: $secretChatId,
        file: new LocalFile('voice.ogg')
    );
}

Send Stickers

public function sendSecretSticker(int $secretChatId): void
{
    $this->sendSticker(
        peer: $secretChatId,
        file: new LocalFile('sticker.webp'),
        mimeType: 'image/webp'
    );
}

Send GIFs

public function sendSecretGif(int $secretChatId): void
{
    $this->sendGif(
        peer: $secretChatId,
        file: new LocalFile('animation.gif'),
        caption: 'Secret animation'
    );
}

Downloading Encrypted Media

Download from Secret Messages

#[Handler]
public function downloadSecretMedia(Incoming&SecretMessage $message): void
{
    if ($message->media) {
        // Download and decrypt automatically
        $path = $message->media->downloadToDir('/tmp');
        
        $this->logger("Downloaded to: $path");
        $message->reply("File received: $path");
        
        // Media is automatically decrypted
        $this->logger("Encrypted: {$message->media->encrypted}"); // true
    }
}

Secret Media Properties

#[Handler]
public function analyzeSecretMedia(Incoming&SecretMessage $message): void
{
    if ($message->media) {
        $media = $message->media;
        
        // Standard properties
        $size = $media->size;
        $fileName = $media->fileName;
        $mimeType = $media->mimeType;
        
        // Encryption properties
        $isEncrypted = $media->encrypted;  // true for secret chats
        $key = $media->key;                // Encryption key
        $iv = $media->iv;                  // Initialization vector
        
        // Secret chat specific
        $thumb = $media->thumb;            // Thumbnail (encrypted)
        $thumbWidth = $media->thumbWidth;  // Thumbnail dimensions
        $thumbHeight = $media->thumbHeight;
        
        // TTL
        $ttl = $media->ttl;  // Self-destruct timer
        
        $this->logger("Encrypted file: $fileName ($size bytes)");
    }
}

Message Properties

Secret messages have all standard message properties:
#[Handler]
public function analyzeSecretMessage(Incoming&SecretMessage $message): void
{
    // Basic properties
    $text = $message->message;        // Message text
    $chatId = $message->chatId;       // Secret chat ID
    $senderId = $message->senderId;   // Sender ID
    $messageId = $message->id;        // Message ID (random_id)
    $date = $message->date;           // Timestamp
    
    // Secret chat specific
    $ttl = $message->ttlPeriod;       // TTL in seconds
    
    // Standard flags
    $isOut = $message->out;           // Is outgoing
    $isReply = $message->isReply();   // Is reply
    
    // Media
    $media = $message->media;         // Encrypted media
    
    $this->logger("Secret message from {$senderId}: $text");
}

Self-Destructing Messages (TTL)

Set TTL for Messages

public function sendWithTTL(int $secretChatId): void
{
    // Message deletes after 5 seconds
    $this->sendMessage(
        peer: $secretChatId,
        message: "Self-destructing in 5 seconds",
        ttl: 5
    );
}

Set Chat-Wide TTL

public function setChatTTL(SecretMessage $message): void
{
    // All messages auto-delete after 1 day
    $message->enableTTL(86400);
    
    // Disable TTL
    $message->disableTTL();
}

TTL for Media

public function sendMediaWithTTL(int $secretChatId): void
{
    // Photo deletes immediately after viewing
    $this->sendPhoto(
        peer: $secretChatId,
        file: new LocalFile('photo.jpg'),
        ttl: 1  // Delete after viewing
    );
    
    // Document deletes after 30 seconds
    $this->sendDocument(
        peer: $secretChatId,
        file: new LocalFile('document.pdf'),
        ttl: 30
    );
}

Complete Secret Chat Example

use danog\MadelineProto\EventHandler\Attributes\Handler;
use danog\MadelineProto\EventHandler\Message\PrivateMessage;
use danog\MadelineProto\EventHandler\Message\SecretMessage;
use danog\MadelineProto\EventHandler\SimpleFilter\Incoming;
use danog\MadelineProto\LocalFile;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Settings;
use danog\MadelineProto\SimpleEventHandler;

class SecretChatBot extends SimpleEventHandler
{
    private array $sent = [];
    
    public const ADMIN = "username";
    
    public function getReportPeers()
    {
        return [self::ADMIN];
    }
    
    /**
     * Handle normal messages - request secret chats
     */
    #[Handler]
    public function handleNormalMessage(Incoming&PrivateMessage $message): void
    {
        if ($message->message === 'request') {
            // Request secret chat
            $this->requestSecretChat($message->senderId);
            $message->reply("Secret chat requested!");
        }
        
        if ($message->message === 'ping') {
            $message->reply('pong');
        }
    }
    
    /**
     * Handle secret chat messages
     */
    #[Handler]
    public function handleSecretMessage(Incoming&SecretMessage $message): void
    {
        // Download media if present
        if ($message->media) {
            $path = $message->media->downloadToDir('/tmp');
            $message->reply("Downloaded to: $path");
        }
        
        // Send welcome messages once per chat
        if (isset($this->sent[$message->chatId])) {
            return;
        }
        
        $chatId = $message->chatId;
        
        // Send photo
        $this->sendPhoto(
            peer: $chatId,
            file: new LocalFile('tests/faust.jpg'),
            caption: 'Encrypted photo from MadelineProto',
        );
        
        // Send photo as document
        $this->sendDocumentPhoto(
            peer: $chatId,
            file: new LocalFile('tests/faust.jpg'),
            caption: 'Photo as document',
        );
        
        // Send GIF
        $this->sendGif(
            peer: $chatId,
            file: new LocalFile('tests/pony.mp4'),
            caption: 'Encrypted GIF',
        );
        
        // Send sticker
        $this->sendSticker(
            peer: $chatId,
            file: new LocalFile('tests/lel.webp'),
            mimeType: "image/webp"
        );
        
        // Send document
        $this->sendDocument(
            peer: $chatId,
            file: new LocalFile('tests/60'),
            fileName: 'encrypted-file'
        );
        
        // Send video
        $this->sendVideo(
            peer: $chatId,
            file: new LocalFile('tests/swing.mp4'),
        );
        
        // Send audio
        $this->sendAudio(
            peer: $chatId,
            file: new LocalFile('tests/mosconi.mp3'),
        );
        
        // Send voice
        $this->sendVoice(
            peer: $chatId,
            file: new LocalFile('tests/mosconi.mp3'),
        );
        
        // Send multiple messages
        for ($i = 0; $i < 10; $i++) {
            $this->logger("Sending message $i to $chatId");
            $this->sendMessage(
                peer: $chatId,
                message: "Message #$i"
            );
        }
        
        $this->sent[$chatId] = true;
    }
}

// Configure and start
$settings = new Settings;
$settings->getLogger()->setLevel(Logger::ULTRA_VERBOSE);

SecretChatBot::startAndLoop('user.madeline', $settings);

Secret Chat Settings

Configure secret chat behavior:
use danog\MadelineProto\Settings;
use danog\MadelineProto\Settings\SecretChats;

$settings = new Settings;
$secretSettings = $settings->getSecretChats();

// Secret chats are enabled by default

Security Features

Encryption

  • Algorithm: MTProto 2.0 with AES-256-IGE
  • Key Exchange: Diffie-Hellman key exchange
  • Perfect Forward Secrecy: New keys for each session
  • Verification: Visual emoji-based key verification

Screenshot Detection

Secret chats notify when screenshots are taken (on supported platforms).

Best Practices

Secret chats only work with user accounts, not bots:
if ($this->isSelfBot()) {
    // Cannot use secret chats as bot
    return;
}
Secret chats are device-specific and may fail if:
  • User is offline
  • User switches devices
  • Network issues occur
Always handle these cases gracefully.
Use appropriate TTL values:
  • 1-2 seconds: View-once media
  • 10-60 seconds: Temporary messages
  • 86400 (1 day): Daily cleanup
All media is automatically encrypted:
  • No plaintext on disk
  • Secure key storage
  • Automatic decryption on download

Limitations

  • User accounts only - Bots cannot use secret chats
  • Device-specific - Chat exists only on creating device
  • No cloud backup - Messages not stored on servers
  • No forwarding - Messages cannot be forwarded
  • Single device - Cannot access from multiple devices
  • No groups - Only 1-on-1 chats

Debugging Secret Chats

Enable verbose logging:
use danog\MadelineProto\Logger;
use danog\MadelineProto\Settings;

$settings = new Settings;
$settings->getLogger()->setLevel(Logger::ULTRA_VERBOSE);

// This will log all encryption operations

Build docs developers (and LLMs) love