diff --git a/config.go b/config.go
index cb7242651f8b499a2720b7415e4006b762d36da5..821cb23d4542de6f1abf6b26e72bfacf03ecf72d 100644
--- a/config.go
+++ b/config.go
@@ -8,7 +8,10 @@ import (
 	"strconv"
 )
 
-var administrators map[string]bool
+var (
+	administrators map[string]bool
+	guildID        string
+)
 
 type conf struct {
 	System  systemConf  `toml:"system"`
@@ -32,9 +35,11 @@ type systemConf struct {
 }
 
 type discordConf struct {
+	BotToken       string `toml:"bot_token"`
 	ClientID       string `toml:"client_id"`
 	ClientSecret   string `toml:"client_secret"`
 	Administrators []int  `toml:"administrators"`
+	GuildID        int    `toml:"guild_id"`
 }
 
 var (
@@ -79,6 +84,10 @@ func confLoad() {
 			administrators[strconv.Itoa(id)] = true
 		}
 	}
+
+	if config.Discord.GuildID > 0 {
+		guildID = strconv.Itoa(config.Discord.GuildID)
+	}
 }
 
 var defConf = conf{
@@ -100,8 +109,10 @@ var defConf = conf{
 		Secret:  "RANDOM_STRING",
 	},
 	Discord: discordConf{
+		BotToken:       "",
 		ClientID:       "",
 		ClientSecret:   "",
 		Administrators: []int{-1},
+		GuildID:        -1,
 	},
 }
diff --git a/routes.go b/routes.go
index 96b2ada7e0d5b41483d5088b72166015dc3af338..9cc97292de9542a355d0e2af08d4ac764ff97f80 100644
--- a/routes.go
+++ b/routes.go
@@ -120,6 +120,12 @@ func registerRoutes() {
 			return
 		}
 
+		if err := ensureJoinEnrollment(context, user); err != nil {
+			log.Printf("error ensuring guild membership of user %s: %s", user.ID, err)
+			context.String(http.StatusInternalServerError, "Internal Server Error")
+			return
+		}
+
 		if s, err := existsTeam(t.ID, user.ID); err != nil {
 			log.Printf("error querying team existance for team %s in tournament %s: %s",
 				user.ID, t.ID.String(), err)
diff --git a/user.go b/user.go
index 7445463114f77547dbc0a47308143c3214db4fa5..2e81f5e639f19ea01d8a4a76202bcbab793df611 100644
--- a/user.go
+++ b/user.go
@@ -2,13 +2,16 @@ package main
 
 import (
 	"fmt"
+	"git.randomchars.net/levatax/tournament-website/oauth"
 	"github.com/bwmarrin/discordgo"
 	"github.com/gin-gonic/gin"
 	"github.com/google/uuid"
 	json "github.com/json-iterator/go"
 	"github.com/syndtr/goleveldb/leveldb"
+	"io"
 	"log"
 	"net/http"
+	"strings"
 	"sync"
 )
 
@@ -224,6 +227,32 @@ func getAuthButton(user *discordgo.User, logout bool) (authText, authRef string)
 	return
 }
 
+func ensureJoinEnrollment(context *gin.Context, user *discordgo.User) error {
+	if req, err := http.NewRequest(http.MethodPut,
+		fmt.Sprintf("https://discordapp.com/api/guilds/%s/members/%s", guildID, user.ID),
+		strings.NewReader(fmt.Sprintf(`{"access_token": "%s"}`,
+			oauth.GetToken(context).AccessToken))); err != nil {
+		return err
+	} else {
+		req.Header.Set("Content-Type", "application/json; charset=utf-8")
+		req.Header.Set("Authorization", "Bot "+config.Discord.BotToken)
+		var resp *http.Response
+		if resp, err = http.DefaultClient.Do(req); err != nil {
+			return err
+		} else {
+			var body []byte
+			if body, err = io.ReadAll(resp.Body); err != nil {
+				return err
+			} else {
+				if resp.StatusCode != 201 && resp.StatusCode != 204 {
+					return fmt.Errorf("discord returned %d: %s", resp.StatusCode, string(body))
+				}
+			}
+			return resp.Body.Close()
+		}
+	}
+}
+
 func validateEnrollment(payload *userEnrollment) bool {
 	return len(payload.TeamName) <= 64 &&
 		len(payload.FirstName) > 0 && len(payload.FirstName) <= 64 &&
diff --git a/web.go b/web.go
index e7f75d44d05e5433c83c9d8d910cf82df30cd7d4..f1d1f520fb0e217dbfa5c0bf0e98d2e235e521da 100644
--- a/web.go
+++ b/web.go
@@ -79,7 +79,10 @@ func webSetup() {
 		ClientSecret: config.Discord.ClientSecret,
 		Endpoint:     oauth.Endpoint(),
 		RedirectURL:  config.Server.BaseURL + "auth/callback",
-		Scopes:       []string{oauth.ScopeIdentify},
+		Scopes: []string{
+			oauth.ScopeIdentify,
+			oauth.ScopeGuildsJoin,
+		},
 	}
 	registerRoutes()