Discord webhooks let you send automated messages to text channels without a full bot. OAuth2 is Discord’s standard for authorizing third-party applications to access user data. Combining both allows you to build a secure system where an external app sends notifications to a specific Discord channel only after a user grants permission via OAuth2. This article explains how to set up a Discord application, generate an OAuth2 authorization URL, and use the resulting access token to send webhook messages.
Key Takeaways: Discord Webhook + OAuth2 Authorization
- Discord Developer Portal > Applications > OAuth2 > URL Generator: Create an authorization URL with the webhook.incoming scope to let users approve your app.
- OAuth2 redirect URI: Your app receives a temporary code that must be exchanged for an access token using a POST request to Discord’s token endpoint.
- Webhook URL in the access token response: The token response includes a webhook object containing the channel ID and the full webhook URL for sending messages.
How Discord Webhook Authorization Works With OAuth2
Discord webhooks are simple HTTP endpoints that accept JSON payloads. Normally you create a webhook manually in a channel’s Integrations settings and copy its URL. That URL contains a unique ID and token that anyone can use to post messages without further authentication.
OAuth2 adds a layer of user consent. Instead of sharing a static webhook URL, your application requests permission from a Discord user to create a webhook on their behalf. The user clicks an authorization link, logs in, and approves the webhook.incoming scope. Discord then returns an access token that your app can use to create a webhook in a specific channel of the user’s choosing.
Prerequisites
Before you begin, you need a Discord application registered in the Developer Portal. You also need a web server or a serverless function that can handle HTTP requests and store tokens securely. The user who authorizes the application must have the Manage Webhooks permission in the target Discord server.
Steps to Authorize a Webhook Using OAuth2
This process has three phases: creating the authorization URL, exchanging the code for a token, and using the token to send a webhook message.
Phase 1: Generate the OAuth2 Authorization URL
- Open the Discord Developer Portal
Go to https://discord.com/developers/applications and sign in with your Discord account. - Select your application
Click the application you want to use. If you have not created one, click New Application, give it a name, and confirm. - Navigate to OAuth2 settings
In the left sidebar, click OAuth2. Under the General tab, you see the Client ID and Client Secret. Copy the Client ID — you need it for the authorization URL. - Add a redirect URI
Scroll down to Redirects. Click Add Redirect and enter the URL where Discord sends the authorization code. For local testing, usehttp://localhost:3000/callback. Click Save Changes. - Build the authorization URL
Use the URL Generator at the top of the OAuth2 page. Select the webhook.incoming scope. Under Redirect URL, choose the redirect you just added. The generator shows a URL that looks like this:https://discord.com/api/oauth2/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=webhook.incoming
Copy this URL. When a user visits it, Discord asks them to select a server and channel where the webhook will be created.
Phase 2: Exchange the Authorization Code for an Access Token
- Handle the redirect
After the user approves the authorization, Discord redirects their browser to your redirect URI with acodequery parameter. For example:http://localhost:3000/callback?code=abc123. - Send a POST request to the token endpoint
Your backend must exchange this code for an access token. Send a POST request tohttps://discord.com/api/oauth2/tokenwith the following body parameters (use URL-encoded form data):client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=authorization_code&code=abc123&redirect_uri=YOUR_REDIRECT_URI - Parse the response
Discord returns a JSON object containingaccess_token,token_type(alwaysBearer),expires_in,refresh_token, and awebhookobject. Thewebhookobject includesid,type,name,avatar,channel_id, andurl. Theurlis the full webhook URL you will use to send messages. - Store the token and webhook URL
Save theaccess_tokenand thewebhook.urlin your database. The token expires after 7 days. Use therefresh_tokento obtain a new token without asking the user to reauthorize.
Phase 3: Send a Webhook Message Using the Authorized Webhook
- Prepare the JSON payload
Create a JSON object with acontentfield. For example:{ "content": "Hello from your authorized webhook!" }
You can also includeembeds,username, andavatar_urlfields. - Send the POST request
Use any HTTP client to send a POST request to the webhook URL you stored. The URL looks like:https://discord.com/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN
Set theContent-Typeheader toapplication/json. No additional authentication is needed because the webhook URL itself is the credential. - Check the response
If the request succeeds, Discord returns204 No Content. If you receive a400 Bad Request, check that your JSON is valid. A404means the webhook was deleted or the token is wrong.
Common Mistakes and Limitations
Webhook Not Appearing in the Token Response
If the token response does not include a webhook object, the user did not complete the authorization flow correctly. The most common cause is that the user did not select a channel during the OAuth2 consent screen. Remind the user to choose a channel before clicking Authorize.
Access Token Expires Before You Send a Message
The access token expires after 7 days. Once you have the webhook URL, you do not need the access token to send messages. The webhook URL is permanent unless the webhook is deleted. Store the webhook URL immediately after the OAuth2 flow and use it directly.
User Cannot Select the Server or Channel
The user must have the Manage Webhooks permission in the server where they want to create the webhook. If the server dropdown is empty, the user lacks this permission in every server they own or manage. Ask the user to check their roles in the target server.
Bot Scope vs Webhook.incoming Scope
Do not confuse the bot scope with webhook.incoming. The bot scope adds a bot user to the server, which requires additional permissions and a bot token. The webhook.incoming scope only creates a webhook — no bot user is added. Use webhook.incoming when you only need to send messages programmatically.
OAuth2 Webhook Scope vs Bot Scope
| Item | webhook.incoming Scope | bot Scope |
|---|---|---|
| What it creates | A webhook URL for sending messages | A bot user that joins the server |
| Permissions needed | Manage Webhooks on the target channel | Defined in the bot’s permission integer |
| Authentication method | Webhook URL (ID + token) | Bot token from the Developer Portal |
| Can read messages | No | Yes, with appropriate intents |
| Can edit/delete messages | No | Yes, if it sent them |
| User consent required | Yes, via OAuth2 authorization | Yes, via OAuth2 authorization |
| Best for | Notifications, logs, simple message posting | Interactive commands, moderation, complex logic |
Use the webhook.incoming scope when your application only needs to send messages to a Discord channel and does not need to read or interact with users. Use the bot scope when you need a full interactive bot that can listen to events, respond to commands, and manage server resources.
The OAuth2 flow for a bot scope is identical to the webhook flow, except the scope value is bot and you must also specify the permissions parameter as a bitwise integer. The token returned is a bot token, not a webhook URL. You can combine both scopes in a single authorization request by separating them with a space, for example: scope=bot webhook.incoming.
You can now set up a Discord webhook authorized through OAuth2 for your application. The key steps are generating the correct authorization URL with the webhook.incoming scope, handling the callback to exchange the code for a token, and storing the webhook URL for future message sending. For production use, implement token refreshing with the refresh_token to avoid asking users to reauthorize every 7 days. You can also combine the webhook.incoming scope with other scopes like identify to access additional user information in the same OAuth2 flow.