diff --git a/config.go b/config.go
index f099886dd0f21cf731c494247089b51056846592..25ca85c27e976b8eb08caf2de9189126193dd860 100644
--- a/config.go
+++ b/config.go
@@ -116,7 +116,7 @@ var defConf = conf{
 	},
 	Discord: discordConf{
 		Token:             "INSERT_TOKEN_HERE",
-		NameFormat:        "$USER#$DISCRIMINATOR",
+		NameFormat:        "***$USER#$DISCRIMINATOR***",
 		StickerText:       true,
 		NoMentionEveryone: true,
 	},
diff --git a/discord.go b/discord.go
index b07bd70732c299df0c7837a41434bfd2420351a0..d3bd7fb4f6222beb8184c614924e65e91f3ab758 100644
--- a/discord.go
+++ b/discord.go
@@ -1,8 +1,11 @@
 package main
 
 import (
+	"fmt"
 	"github.com/bwmarrin/discordgo"
+	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
 	"log"
+	"strings"
 )
 
 var (
@@ -58,8 +61,90 @@ func openDiscord() {
 	ready <- struct{}{}
 }
 
+var idReference = make(map[string]int)
+
 func handleDiscord() {
-	session.AddHandler(func(session *discordgo.Session, create *discordgo.MessageCreate) {
-		// TODO
-	})
+	session.AddHandler(discordHandleCreate)
+}
+
+func discordHandleCreate(session *discordgo.Session, create *discordgo.MessageCreate) {
+	if create.Author == nil || create.Author.ID == session.State.User.ID {
+		return
+	}
+
+	dc, tc := discordGetConf(create.ChannelID)
+	if dc == nil || tc == nil {
+		return
+	}
+
+	setTelegramPreviousCaller(int64(tc.ID), -1)
+
+	if create.Message == nil || create.Message.Author == nil {
+		if config.System.Verbose {
+			log.Printf("got create with missing fields")
+		}
+		return
+	}
+
+	msg := tgbotapi.NewMessage(int64(tc.ID), "")
+	msg.ParseMode = "Markdown"
+
+	if create.Message.Content == "" && len(create.Message.Attachments) == 0 {
+		// FIXME: implement stickers after library supports it
+
+		if config.System.Verbose {
+			log.Printf("got message %s with empty content", create.Message.ID)
+		}
+		return
+	} else {
+		msg.Text = create.Message.Content
+		if strings.HasPrefix(msg.Text, "<") && strings.HasSuffix(msg.Text, ">") {
+			msg.DisableWebPagePreview = true
+		}
+	}
+
+	if reference := create.MessageReference; reference != nil {
+		if tid, ok := idReference[reference.MessageID]; ok {
+			msg.ReplyToMessageID = tid
+		} else if tid, ok = referenceMessage[reference.MessageID]; ok {
+			msg.ReplyToMessageID = tid
+		}
+	}
+
+	msg.Text = discordMakeHeader(create.Author) + msg.Text
+
+	for i, attachment := range create.Message.Attachments {
+		msg.Text += fmt.Sprintf("\n[Attachment %v](%s)", i, attachment.URL)
+	}
+
+	if m, err := botAPI.Send(msg); err != nil {
+		log.Printf("error relaying message %s, %s", create.Message.ID, err)
+		return
+	} else {
+		idReference[create.Message.ID] = m.MessageID
+	}
+
+	log.Printf("D%vM%s -> T%vM%v %s#%s (%s): %s",
+		dc.ID, create.Message.ID, tc.ID, idReference[create.Message.ID],
+		create.Message.Author.Username, create.Message.Author.Discriminator, create.Message.Author.ID,
+		create.Message.Content)
+}
+
+func discordGetConf(id string) (dc, tc *bridgePlatformConf) {
+	if c, ok := discordBridge[id]; !ok {
+		if config.System.Verbose {
+			log.Printf("got event from unconfigured channel %s", id)
+		}
+		return
+	} else {
+		dc = &c.Discord
+		tc = &c.Telegram
+	}
+	return
+}
+
+func discordMakeHeader(user *discordgo.User) string {
+	return strings.ReplaceAll(strings.ReplaceAll(config.Discord.NameFormat,
+		"$USER", user.Username),
+		"$DISCRIMINATOR", user.Discriminator) + "\n"
 }
diff --git a/go.sum b/go.sum
index b92f49b5f2533b4a4f6248a9fa307447502e1f7f..91e7f098756aba180da607cd0eb295c2cfd8ae92 100644
--- a/go.sum
+++ b/go.sum
@@ -4,6 +4,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
 github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
+github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.0.0-rc1 h1:Mr8jIV7wDfLw5Fw6BPupm0aduTFdLjhI3wFuIIZKvO4=
+github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.0.0-rc1/go.mod h1:2s/IzRcxCszyNh760IjJiqoYHTnifk8ZeNYL33z8Pww=
 github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/pelletier/go-toml/v2 v2.0.0-beta.3 h1:PNCTU4naEJ8mKal97P3A2qDU74QRQGlv4FXiL1XDqi4=
diff --git a/telegram.go b/telegram.go
index 832d7e90db5e4ffdb8ff23ed1d3c04361c994cc0..f2a98615ac60ad1b961be23c385aadb59c03deec 100644
--- a/telegram.go
+++ b/telegram.go
@@ -72,18 +72,19 @@ func handleTelegram() {
 	}
 }
 
-var previousCaller = make(map[int64]int)
+var telegramPreviousCaller = make(map[int64]int)
 
-func setPreviousCaller(cid int64, uid int) bool {
-	if previousCaller[cid] == uid {
+func setTelegramPreviousCaller(cid int64, uid int) bool {
+	if telegramPreviousCaller[cid] == uid {
 		return true
 	}
-	previousCaller[cid] = uid
+	telegramPreviousCaller[cid] = uid
 	return false
 }
 
 var (
 	messageReference = make(map[int]*discordgo.MessageReference)
+	referenceMessage = make(map[string]int)
 	hasHeader        = make(map[int]bool)
 )
 
@@ -159,7 +160,7 @@ func respondTelegram(update tgbotapi.Update) {
 	if update.Message.ReplyToMessage != nil {
 		if r, ok := messageReference[update.Message.ReplyToMessage.MessageID]; ok {
 			reference = r
-			setPreviousCaller(update.Message.Chat.ID, -1)
+			setTelegramPreviousCaller(update.Message.Chat.ID, -1)
 		}
 	}
 
@@ -187,7 +188,7 @@ func respondTelegram(update tgbotapi.Update) {
 	}
 
 	if update.Message.Sticker != nil {
-		content := header
+		content := ""
 		if config.Telegram.StickerEmoji {
 			content += update.Message.Sticker.Emoji
 		}
@@ -224,6 +225,7 @@ func respondTelegram(update tgbotapi.Update) {
 		ChannelID: dChannelID,
 		GuildID:   dGuildID,
 	}
+	referenceMessage[dMessageID] = update.Message.MessageID
 	hasHeader[update.Message.MessageID] = has
 
 	log.Printf("T%vM%v -> D%vM%s @%s (%v): %s",
@@ -231,8 +233,7 @@ func respondTelegram(update tgbotapi.Update) {
 }
 
 func telegramMakeHeader(message *tgbotapi.Message, force bool) (string, bool) {
-	if !config.System.DisplaceHeader || force || !setPreviousCaller(message.Chat.ID, message.From.ID) {
-		// TODO: check the discord side for header displace
+	if !config.System.DisplaceHeader || force || !setTelegramPreviousCaller(message.Chat.ID, message.From.ID) {
 		space := ""
 		if message.From.LastName != "" {
 			space = " "
@@ -311,6 +312,7 @@ func telegramHandleFile(dc bridgePlatformConf, update tgbotapi.Update, fileID st
 		ChannelID: *dChannelID,
 		GuildID:   *dGuildID,
 	}
+	referenceMessage[*dMessageID] = update.Message.MessageID
 	hasHeader[update.Message.MessageID] = has
 
 	return true