Discord Webhooks Guide

This guide includes:

Translations

JSON

If you do not know anything about JSON, please, spend some time on learning JSON structure.

What is JSON?

JSON stands for

  • Java
  • Script
  • Object
  • Notation

N.B. Don't be scared. JSON is easy to learn and use! This is not a programming language!!!

JSON is data-storing format that easy to read and write for humans and robots.

  • key: value - this is key-value pair.
  • Key and value are separated with semicolon (:), no exceptions.
  • Multiple key-values are separated with comma (,), no exceptions.
  • Spaces and line breaks are ok till they not the part of key or value.
  • The key is always text inserted between double quotes (" ").
  • But value can be different types:
    • string - "sample text", "cool\nthings"
    • number - 42, -300, 6.62e-34
    • object - { "name": "Jason", "likes": ["apples", "oranges"] }
    • array - ["apple", "banana", "orange"], [1, true, 3, "meow"]
    • boolean - true, false
    • null - null

About strings

Strings can store any characters you want, but some of them need to be escaped:

  • double quote - \"
  • slash - \\
  • backslash - \/ (escaping is optional)
  • newline - \n (use this if you want to add newline to value)
  • carriage return - \r
  • horizontal tab - \t
  • backspace - \b
  • form feed - \f
  • unicode character - \uxxxx

Example

{
  "name": "Pumpkin",
  "age": 7,
  "likes": [
    "patting",
    "sleeping in a garden",
    "salmon"
  ],
  "appearance": "Orange Tabby",
  "owner_name": "Jane Doe",
  "phone_number": "+447712345678",
  "address": {
    "country": "England",
    "city": "London",
    "street": "Crown Street",
    "house": 38,
    "notes": null
  }
}

Structure of Webhook

Before using Webhooks you have to know the structure. All elements listed here are optional but request body should contain content, embeds or attachments, otherwise request will fail.

  • username - overrides the predefined username of the webhook
  • avatar_url - overrides the predefined avatar of the webhook
  • content - text message, can contain up to 2000 characters
  • embeds - array of embed objects. In comparison with bots, webhooks can have more than one custom embed
    • color - color code of the embed. You have to use Decimal numeral system, not Hexadecimal. You can use SpyColor for that. It has decimal number converter.
    • author - embed author object
      • name - name of author
      • url - url of author. If name was used, it becomes a hyperlink
      • icon_url - url of author icon
    • title - title of embed
    • url - url of embed. If title was used, it becomes hyperlink
    • description - description text
    • fields - array of embed field objects
      • name - name of the field
      • value - value of the field
      • inline - if true, fields will be displayed in the same line, 3 per line, 4th+ will be moved to the next line
    • thumbnail - embed thumbnail object
      • url - url of thumbnail
    • image - embed image object
      • url - image url
    • footer - embed footer object
      • text - footer text, doesn't support Markdown
      • icon_url - url of footer icon
    • timestamp - ISO8601 timestamp (yyyy-mm-ddThh:mm:ss.msZ)
  • tts - makes message to be spoken as with /tts command
  • allowed_mentions - object allowing to control who will be mentioned by message
    • parse - array, can include next values: "roles", "users" and "everyone", depends on which decides which mentions work. If empty, none mention work.
    • roles - array, lists ids of roles which can be mentioned with message, remove "roles" from parse when you use this one.
    • users - array, lists ids of roles which can be mentioned with message, remove "users" from parse when you use this one.

Webhook example

{
  "username": "Webhook",
  "avatar_url": "https://i.imgur.com/4M34hi2.png",
  "content": "Text message. Up to 2000 characters.",
  "embeds": [
    {
      "author": {
        "name": "Birdie♫",
        "url": "https://www.reddit.com/r/cats/",
        "icon_url": "https://i.imgur.com/R66g1Pe.jpg"
      },
      "title": "Title",
      "url": "https://google.com/",
      "description": "Text message. You can use Markdown here. *Italic* **bold** __underline__ ~~strikeout~~ [hyperlink](https://google.com) `code`",
      "color": 15258703,
      "fields": [
        {
          "name": "Text",
          "value": "More text",
          "inline": true
        },
        {
          "name": "Even more text",
          "value": "Yup",
          "inline": true
        },
        {
          "name": "Use `\"inline\": true` parameter, if you want to display fields in the same line.",
          "value": "okay..."
        },
        {
          "name": "Thanks!",
          "value": "You're welcome :wink:"
        }
      ],
      "thumbnail": {
        "url": "https://upload.wikimedia.org/wikipedia/commons/3/38/4-Nature-Wallpapers-2014-1_ukaavUI.jpg"
      },
      "image": {
        "url": "https://upload.wikimedia.org/wikipedia/commons/5/5a/A_picture_from_China_every_day_108.jpg"
      },
      "footer": {
        "text": "Woah! So cool! :smirk:",
        "icon_url": "https://i.imgur.com/fKL31aD.jpg"
      }
    }
  ]
}

And how it looks

webhook result example

username

Overrides webhook's username. Useful, if you're using the same Webhook URL for several things.

Example:

{
  "username": "Cat",
  "content": "Hello!"
}

username example

avatar_url

Overrides webhook's avatar. Useful, if you're using the same Webhook URL for several things.

Example:

{
  "avatar_url": "https://i.imgur.com/oBPXx0D.png",
  "content": "Woof-woof!"
}

avatar url example

content

Sets content for message sent by webhook.

Example:

{
  "content": "*reads manual*"
}

content example

embeds

Sets custom embeds for message sent by webhook. embeds is an array of embeds and can contain up to 10 embeds in the same message.

Examples:

{
  "embeds": [{
    "title": "Hello!",
    "description": "Hi! :grinning:"
  }]
}

embeds example

{
  "embeds": [
    {
      "title": "Meow!",
      "color": 1127128
    },
    {
      "title": "Meow-meow!",
      "color": 14177041
    }
  ]
}

embeds example

P.S.

Adding embeds overrides url embeds.

color

Sets color for webhook's embed. It equals 0 (transparent) by default. Color requires number instead hex code, so you have to convert hexadecimal color code to decimal number. Color can be defined as number 65280 and as string "65280".

I recommend to use SpyColor for color picking, it provides decimal value.

Example:

{
  "embeds": [
    {
      "title": "Meow!",
      "color": 1127128
    },
    {
      "title": "Meow-meow!",
      "color": "14177041"
    }
  ]
}

color example

author

Adds Author block to embed. author is an object which includes three values:

  • name - sets name.
  • url - sets link. Requires name value. If used, transforms name into hyperlink.
  • icon_url - sets avatar. Requires name value.

Example:

{
  "embeds": [{
    "author": {
      "name": "Delivery Girl",
      "url": "https://www.reddit.com/r/Pizza/",
      "icon_url": "https://i.imgur.com/V8ZjaMa.jpg"
    },
    "description": "Your pizza is ready!\n:timer:ETA: 10 minutes."
  }]
}

author example

title

Sets title for webhook's embed.

Example:

{
  "embeds": [{
    "title": "Meow!"
  }]
}

title example

url

Sets link for title in your webhook message. Requires title variable and turns it into hyperlink.

Example:

{
  "embeds": [{
    "title": "Google it!",
    "url": "https://google.com/"
  }]
}

url example

description

Sets description for webhook's embed.

Example:

{
  "embeds": [{
    "description": "*Hi!* **Wow!** I can __use__ hyperlinks [here](https://discord.com)."
  }]
}

description example

fields

Allows you to use multiple title + description blocks in embed. fields is an array of field objects. Each object includes three values:

  • name - sets name for field object. Required;
  • value - sets description for field object. Required;
  • inline - if true then sets field objects in same line, but if you have more than 3 objects with enabled inline or just too long you will get rows with 3 fields in each one or with 2 fields if you used thumbnail object. false by default. Optional.

P.S. You can use up to 25 fields in same embed. name and value support Discord Markdown.

Example:

{
  "embeds": [{
    "fields": [
      {
        "name": "Cat",
        "value": "Hi! :wave:",
        "inline": true
      },
      {
        "name": "Dog",
        "value": "hello!",
        "inline": true
      },
      {
        "name": "Cat",
        "value": "wanna play? join to voice channel!"
      },
      {
        "name": "Dog",
        "value": "yay"
      }
    ]
  }]
}

fields example

image

Allows you to add image to the embed. Currently, there is no way to set width/height of the image. url value must be valid url that starts with http(s):// or attachment://, more info about the latter one can be found on the file page.

Example:

{
  "embeds": [{
    "image": {
      "url": "https://i.imgur.com/ZGPxFN2.jpg"
    }
  }]
}

image

thumbnail

Allows you to add thumbnail to the embed. Currently, there is no way to set width/height of the thumbnail. url value must be valid url that starts with http(s):// or attachment://, more info about the latter one can be found on the file page.

Example:

{
  "embeds": [{
    "thumbnail": {
      "url": "https://upload.wikimedia.org/wikipedia/commons/3/38/4-Nature-Wallpapers-2014-1_ukaavUI.jpg"
    }
  }]
}

thumbnail example

footer

Allows you to add footer to embed. footer is an object which includes two values:

  • text - sets name for author object. Markdown is disabled here!!!
  • icon_url - sets icon for author object. Requires text value.

Example:

{
  "embeds": [{
      "footer": {
        "text": "Woah! *So cool!* :smirk:",
        "icon_url": "https://i.imgur.com/fKL31aD.jpg"
      },
      "description": "Your pizza is ready!\n:timer:ETA: 10 minutes."
  }]
}

footer example

timestamp

Allows you to add timestamp to embed. Time stores as String in the next format: "YYYY-MM-DDTHH:MM:SS.MSSZ". If footer was used they will be separated with a bullet (•). Also, this is special field, because it can show different time based on user's device.

P.S. Timestamp is not just text. This is formatted UTC time and date. It will show different time because timezones. Look on example below: I set 12pm but it shows 2pm because UTC+2 timezone.

Example:

{
  "embeds": [{
    "description": "Time travel!",
    "timestamp": "2015-12-31T12:00:00.000Z"
  }]
}

timestamp example

tts

Enables text-to-speech for current message, so everyone who have tts enabled with hear it.

Example:

{
  "content": "Hi",
  "tts": true
}

allowed_mentions

Allows to suppress pings by users, roles or everyone/here mentions. allowed_mentions object and can contain next parameters:

  • parse - array and can include next values:
    • "everyone" - if present everyone/here will ping.
    • "users" - if present user mentions will ping.
    • "roles" - if present role mentions will ping.
  • users - array with id of users, allows to limit which users may be pinged.
  • roles - array with id of roles, allows to limit which roles may be pinged.

Don't include users and roles both in allowed_mentions and parse, , if you want

Here's some examples:

  • nobody will be pinged by this message.

    {
      "content": "@everyone <@&role-id> <@user-id>",
      "allowed_mentions": { "parse": [] }
    }
    
  • only users that didn't suppress everyone/here mentions will be pinged.

    {
      "content": "@everyone <@&role-id> <@user-id>",
      "allowed_mentions": { "parse": ["everyone"] }
    }
    
  • only user with user-id will be pinged, in this case @everyone won't ping anyone. user2 mention is not in content so no ping for them.

    {
      "content": "@everyone <@&role-id> <@user-id>",
      "allowed_mentions": { "users": ["user-id", "user2-id"]  }
    }
    
  • similar for roles.

    {
      "content": "@everyone <@&role-id> <@user-id>",
      "allowed_mentions": { "roles": ["role-id"] }
    }
    
  • Will ping everyone with disabled everyone/here mention suppress, all users mentioned in message (i. e. user-id user) or with role-id role (unless enabled role mention suppress).

    {
      "content": "@everyone <@&role-id> <@user-id>",
      "allowed_mentions": {
        "parse": ["everyone", "users"],
        "roles": ["role-id"]
      }
    }
    

Discord API reference

file

Sending files using webhooks is only possible using Content-Type: multipart/form-data header. Using this method also means you have to set json body as value of payload_json parameter.

Parameter names should have unique names otherwise they will collide and only first file from ones with identical names will be shown.

Example:

file1=@cat.jpg
file2=@dog.jpg
payload_json={"embeds":[{"title":"test"}]}

result example

Embedding attachments

To put image attachment inside embed use attachment:// with its filename (not field name). That will also hide attachment from the message.

file1=@pizza.jpg
payload_json={"embeds":[{"image":{"url":"attachment://pizza.jpg"}}]}

result example

How to send attachments using various tools explained on respective pages:

IFTTT

IFTTT is a very awesome service for connecting services. It supports Webhooks, so we can use it with Discord.

Create an Account on IFTTT

Visit IFTTT and create yourself an account (if you don't have one yet).

Create Webhook on Discord

  1. Go to Server or Channel settingsIntegrationsWebhooksCreate Webhook or Click on existing one if you created one already.
  2. Setup name, avatar and the channel in which webhook messages will be sent. When ready, click Save Changes and Copy Webhook URL.

⚠️ Attention! Keep URL safe, don't share them with people you don't trust, don't post it in public channels, don't give your server mods Administrator or Manage Webhooks permission as it gives access to them as well. Webhooks are quite powerful and can cause some troubles, which including, but not limited to, @everyone/@here mentions (webhooks ignore channel permissions), message/image spam (webhooks have max 5 requests in 2 seconds window rate limit, but still it's 150 requests per minute) and requests can't be traced, so finding the guilty is pretty much impossible, so you're warned I guess... If something like this happen, remove webhook causing problems and it won't be able to send messages anymore.

Creating an Applet

If this

  1. Go to IFTTT website ➤ click CreateApplets.
  2. Click [+]this.
  3. Choose a service you want accept updates/feeds from, use search to find it faster.
  4. If you connect service in the first time it may ask you to authorize, just follow the steps.
  5. Most of services have multiple triggers. Choose the one that fits your needs.
  6. Fill the fields. Here can be more than one step. Read the descriptions and examples.

Then that

  1. Click [+]that
  2. Search for Webhooks with the search bar.
  3. Choose Make a web request as action.
  4. Paste the Webhook URL in URL field.
  5. Select POST as Method.
  6. Select application/json as Content Type.
  7. Body is the main part. there's couple of things you have to know:
  • Neither IFTTT nor Discord build the result message, you're the one responsible for it.
  • Request body is written in JSON, if you have no idea what it is, please check JSON reference page first, then structure pages.
  • All available Ingredients are listed under Add Ingredient dropdown menu. If something Ingredient not listed means you can't use it and it will cause error on proceeding. Also, make sure you put spaces between {{ }} which are not part of Ingredient, like here: {"embeds": [{"image": {"url": "{{ImageUrl}}"}}]}{"embeds": [{"image": {"url": "{{ImageUrl}}"} }]} otherwise they may be assumed as end of Ingredient name and break the validation.
  • Due to certain specifics of JSON format the Ingredients IFTTT provides may break the request. Because that escaping Ingredients is highly recommended! To escape Ingredient, add <<< & >>> around {{Ingredient}}<<<{{Ingredient}}>>> (website says <<>>, but that a typo). Only times when escaping rule may be ignored are when Ingredient is 100% URL and when Ingredient is part of URL and that Ingredient only consists of URL-safe characters, like: https://twitter.com/{{Username}} (in some cases escaping these were causing broken requests).
  • You probably noticed when you post link in channel the embed appears under the link, it works in the same way with Webhooks! (adding embeds to body or adding <> around links disables that behavior though). So sometimes, instead of building complex request body {"content": "{{Url}}"} (Url is placeholder, it may be called differently between services) can be more than enough!
  • Always check JSON for being valid! You can use text editors with JSON support, Embed Visualizer by leovoel which was made exclusively for previewing request bodies for Discord and provides better experience in editing them (default mode is bot mode and it has difference in embeds declaring, make sure Webhook Mode is enabled!), formatting/linting websites like JSON Formatter or JSON Editor Online, etc. Although, this is not guarantee that request won't fail!
  • Certain fields have limit in length and content (url fields may contain only urls, otherwise request fail!), make sure you don't overflow them.
  1. Click Create Action and then Finish.
  2. Done!

Troubleshooting

If you suspect request failed, first check activity logs: AppletSettingsView Activity. Ff there none errors, just messages about applet being created/updated means it wasn't triggered yet, give it some time, but if there's any please check this troubleshooting list:

  • Action failure message: Rate limited by the remote server. - means Discord rate limited this request because IFTTT sends it too frequently. Mostly happens when IFTTT tries to send requests in bulk on same webhook in short amount of time. Discord's webhook rate limit is 5 requests per 2 seconds, keep that in mind.
  • Unable to make web request. Your server returned a 400. - means request is invalid. Can be caused by:
    • wrong method verb (should be POST);
    • wrong content-type (should be application/json);
    • bad request body:
      • empty, non or invalid JSON
      • resulting JSON is broken (usually caused by newlines in ingredients (json doesn't support them in values) and unicode characters (rare, but happens sometimes)), can be fixed by escaping variables with <<<{{ingredient}} or unicode characters>>>;
    • error from server saying that one of fields hit limit (sadly, but ifttt doesn't show error that came from server). Check if json data follow limits here. Try replace ingredients with data from applet logs and send it through Postman, Insomnia, other REST Client.
  • Error 401 - webhook url isn't full, try to copy it again.
  • Error 404 - webhook you're using has been removed. Create new webhook and replace the old URL.
  • Error 405 - happens when you use other than POST methods.
  • There was a problem with the X service. - and usually no data provided for this one. As it says, the problem is on service side, next check should be successful, if not - try reauthorize.

Delay, checks and other stuff

Some services provide close to realtime delay, some are up to 15 minutes, some even longer. Delay in applet description may be misleading and different for everyone, it's always better check by your own. Having multiple applets using same service may cause additional overall delay. Please bare with that as nothing much can be done.

For example for Reddit you may try RSS service as Reddit supports it too (check this), for Twitter/Instagram/etc. you may try services which convert to RSS feed.

IFTTT doesn't have retrospective check, means posts created before applet will be ignored. Check now button performs force check, but for some services it may work differently and don't do any check until cooldown passes.

Here's some services I made for own needs but I think others may find them useful too:

  • Avatar resolver - some services don't provide avatars you might want be showed in webhook messages, now you can), multiple providers supported!
    • usage example: {"avatar_url": "https://avatar.glue-bot.xyz/twitter/{{Username}}"}
  • discord-ifttt - use this if you keep receiving Too many requests to this host error, also applies rate limit so requests will less to fail, allows to use text/plain header, more info on the page!
  • Multi webhook - for times when you want to send request to multiple webhooks using one.
    • usage example: URL: https://multi-webhook.vercel.app/api/multi, JSON: {links: [first_url, second_url], ...} where first_url and second_url are webhook urls.

IFTTT Platform / Filter code

IFTTT Platform allows you to create and publish applets, so you can share it with the others. Also it allows to put script that will execute after applet being triggered, so you can modify output data and skip actions to custom conditions, that script is called Filter code.

Filter code is written in TypeScript, but JavaScript code should work too. If you're relying on type coercion you'll need to manually convert values to right types, code window has build-in linter, it will tell what's wrong.

👉 Update: Before, Filter code was exclusively IFTTT Platform feature, but now it can be used on main website too (Pro subscription required).

Example

// Building JSON body
const body = {
  embeds: [{
    author: {
      name: Trigger.UserName, // equals {{UserName}}
      url: Trigger.LinkToTweet // equals {{LinkToTweet}}
    },
    description: `*${Trigger.Text}*`, // equals *{{Text}}*
    color: 0x1da1f2, // equals 1942002 (hex ➤ decimal value)
    timestamp: Meta.triggerTime // equals moment.js object but during JSON encoding will be turned to YYYY-MM-DDThh:mm:ss.msZ (discord compatible timestamp)
  }]
};

// If you want to manually skip some triggers when some of ingredients match/not match something or allow to run only at certain time you can do this:
if (Trigger.Text.indexOf('skip') > 0) {
  MakerWebhooks.makeWebRequest.skip('this reason will appear in activity logs!');
}

// This action makes Web Request and sends webhook
MakerWebhooks.makeWebRequest.setBody(JSON.stringify(body));
// Done!

TS/JS cheat sheet

//== in ts/js quotes around keys can be omitted
let body = {"content": "text"};
// equals
let body = {content: "text"};

//== the difference between single and double quotes that you need to escape it between them own
let text = ['what\'s up?', "*\"winks\"*"];
// equals
let text = ["what's up?", '*"winks"*'];

//== if you want to add value inside string use *template literals*: ` ` with ${}
let message = 'hello, ' + name + '!';
// equals
let message = `hello, ${name}!`;

//== if variable is a string there's no need to use template literals
let zzz = `${ccc}`;
// equals
let zzz = ccc; // if variable is not string but it should be call ccc.toString() on it!

//== var/let/const
// if value might be reassigned, use let
let favoriteNumber = 42
// otherwise use const
const name = 'John'
// using var is ok too, but less preferable
var happy = true

//== check if string includes substring
let str = 'The quick brown fox jumps over the lazy dog';
let substr = 'fox';
let substr2 = 'cat';
let result = str.indexOf(substr) > -1; // equals true
let result2 = str.indexOf(substr2) > -1; // equals false
// explanation: IFTTT doesn't have str.includes(substr) method available
// so we have to use str.indexOf(substr) > -1 instead,
// which returns coordinates of substring and -1 if not found.
// > -1 is in range [0..+inf), >= 0 will work the same way

//== random value
let random = Math.random(); // returns float value in range [0..1) (1 excluded)
let random2 = Math.round(Math.random()); // returns 0 or 1
let random3 = Math.floor(Math.random() * 10); // returns integer value in range [0..9] (10 excluded)
let random4 = 5 + Math.floor(Math.random() * 45); // returns integer value in range [5..49] (50 excluded)

//== random element in array
let arr = ['one', 'two', 'three', 'four'];
let picked = arr[Math.floor(Math.random() * arr.length)];

Useful snippets

// check if str is part of substr, optionally comparing them case-insensitively
function includes(str: string, substr: string, caseInsensitive?: boolean): boolean {
  if (caseInsensitive) {
    str = str.toLowerCase();
    substr = substr.toLowerCase();
  }
  return str.indexOf(substr) > -1;
}

includes('watermelon', 'melon') // returns true
includes('Watermelon', 'Melon') // returns false
includes('Watermelon', 'Melon', true) // returns true

// random integer number between min and max
function random(min: number, max: number): number {
  return min + Math.floor(Math.random() * (max - min + 1));
}

random(5, 10) // returns value between 5 and 10
random(-5, 5) // returns value between -5 and 5

// select random element of array
function pick<T>(arr: T[]): T {
  return arr[Math.floor(Math.random() * arr.length)];
}

pick(['apple', 'banana', 'orange'])

Postman

Postman is a GUI tool for sending web requests.

Download it from official website. Available for Windows, Linux, and macOS.

Here's how to use it:

  1. Click + on the tabs panel.
  2. Click on dropdown in front of URL field and choose POST.
  3. Paste Webhook URL in URL field.
  4. Click Body tab > raw > JSON from the dropdown.
  5. Paste the body below.
  6. Press Send.
  7. If status shows 204 No Content means request succeed!

postman example

Sending attachments

To send attachment(s):

  1. Switch from raw to form-data.
  2. Hover on key field, click on dropdown and choose File.
  3. Click Select Files and select file (despite it allowing you to choose multiple files, choose one).
  4. Repeat if you want to add more attachments.
  5. To add json to request add key with payload_json name and json body value (dropdown must say Text).

postman example

Insomnia

Insomnia is a GUI tool similar to Postman for sending web requests.

Download it from official website. Available for Windows, Linux, and macOS.

Here's how to use it:

  1. Click + on the left, choose New Request, in dialog window press Create.
  2. Click on dropdown in front of URL field and choose POST.
  3. Paste Webhook URL in URL field.
  4. Click Body dropdown, choose JSON.
  5. Paste the body below.
  6. Press Send.
  7. If status shows 204 No Content means request succeed!

Insomnia example

Sending attachments

To send attachment(s):

  1. Switch Body format from JSON to Multipart Form.
  2. Click on arrow icon, choose File.
  3. Click Choose File and select file.
  4. Repeat if you want to add more attachments.
  5. To add json to request add key with payload_json name and json body value (both Text and Text (Multi-line) are fine).

Insomnia example

curl

curl - command line tool for sending web requests.

Also, you can download executable from their website and run it from a directory or make it discoverable through PATH, so it can be run from anywhere. Using a package manager is preferable as it simplifies the process of installing and updating the tool.

Usage

curl -H "Content-Type: application/json" -d <body> <link>

Bash/Zsh/etc.

# Usually, request also includes `-X POST` to set request verb to POST, but using `-d` does that automatically.
# -H "Content-Type: application/json" - adds header that tells server you're sending JSON data.
# -d '{"username": "test", "content": "hello"}' - sets request data.
curl -H "Content-Type: application/json" -d '{"username": "test", "content": "hello"}' "https://discord.com/api/webhooks/123/w3bh00k_t0k3n"

# To make command more readable you can split it to multiple lines using backslash `\`
# and set webhook url as variable, so you don't need to paste it over and over again.
# Also you can add the variable to your `.*rc` file, so it persists on console reloads.
export WEBHOOK_URL="https://discord.com/api/webhooks/123/w3bh00k_t0k3n"
curl \
  -H "Content-Type: application/json" \
  -d '{"username": "test", "content": "hello"}' \
  $WEBHOOK_URL

PowerShell

# In PowerShell you have to escape " with \ inside strings, so body string be parsed correctly.
$WEBHOOK_URL = "https://discord.com/api/webhooks/123/w3bh00k_t0k3n"
curl -H "Content-Type: application/json" -d '{\"username\": \"test\", \"content\": \"hello\"}' $WEBHOOK_URL

# ` is used for making command multiline
curl `
  -H "Content-Type: application/json" `
  -d '{\"username\": \"test\", \"content\": \"hello\"}' `
  $WEBHOOK_URL

Command Prompt (cmd.exe)

REM Notice double quotes around body and none around link.
SET WEBHOOK_URL=https://discord.com/api/webhooks/123/w3bh00k_t0k3n
curl -H "Content-Type: application/json" -d "{\"username\": \"test\", \"content\":\"hello\"}" %WEBHOOK_URL%

REM ^ is multiline splitter in cmd.
curl ^
  -H "Content-Type: application/json" ^
  -d "{\"username\": \"test\", \"content\": \"hello\"}" ^
  %WEBHOOK_URL%

Sending attachments

# Adding `-H "Content-Type: multipart/form-data"` is not required as `-F` sets it automatically.
# -F 'payload_json={}' - sets json body.
# -F "file1=@cat.jpg" - adds cat.jpg file as attachment.
# -F "file2=@images/dog.jpg" - adds dog.jpg file from images directory.
curl \
  -F 'payload_json={"username": "test", "content": "hello"}' \
  -F "file1=@cat.jpg" \
  -F "file2=@images/dog.jpg" \
  $WEBHOOK_URL

HTTPie

HTTPie is a command line HTTP client, just like curl but more user friendly.

  • Windows - can be installed with pip (requires Python 3.x installed). By the way, this is cross-platform solution.
  • Linux - can be installed with built-in package manager or Homebrew.
  • macOS - can be installed with Homebrew or MacPorts.

Check docs for installation details.

Usage

http <url> <body params>

for Bash/Zsh/etc.

# set url variable to type less and make command more readable
export WEBHOOK_URL="https://discord.com/api/webhooks/123/w3bh00k_t0k3n"
http -j post $WEBHOOK_URL username="Cat" content="meow" embeds:='[{"title": "Cool!"}]'
# -j/--json flag sets json header, but it's used by default so it can be omitted.
http post $WEBHOOK_URL username="Cat" content="meow" embeds:='[{"title": "Cool!"}]'
# Default method is GET, but adding data makes it switch to POST so you can omit it too.
http $WEBHOOK_URL username="Cat" content="meow" embeds:='[{"title": "Cool!"}]'

Depends on type of value you have to use different separators:

  • = - text.
  • := - raw JSON value. Use it for array, number, boolean and nested values.
  • @ - embed file.
  • =@ - embed json file.

Also, if you don't want to mess with these and would like to just pass raw body, like can be done in curl, use the next approach:

echo '{"username": "Cat", "content": "meow", "embeds": [{"title": "Cool!"}]}' | http $WEBHOOK_URL

PowerShell

# In PowerShell you have to escape " with \ inside strings, so body string be parsed correctly.
$WEBHOOK_URL = "https://discord.com/api/webhooks/123/w3bh00k_t0k3n"
http $WEBHOOK_URL username="Cat" content="meow" embeds:='[{\"title\": \"Cool!\"}]'

# Also you can just pass raw json body:
'{"username": "Cat", "content": "meow", "embeds": [{"title": "Cool!"}]}' | http $WEBHOOK_URL

Command Prompt (cmd.exe)

REM Notice escaped double quotes around values and none around link
SET WEBHOOK_URL=https://discord.com/api/webhooks/123/w3bh00k_t0k3n
http %WEBHOOK_URL% username="Cat" content="meow" embeds:="[{\"title\": \"Cool!\"}]"

REM Outer quotes are skipped due to cmd parsing
echo {"username": "Cat", "content": "meow", "embeds": [{"title": "Cool!"}]} | http %WEBHOOK_URL%

Sending attachments

# -f flag sets "Content-Type: multipart/form-data" header.
# payload_json='{}' - sets json body.
# file1@cat.jpg - adds cat.jpg file as attachment.
# file2@images/dog.jpg - adds dog.jpg file from images directory.
http -f $WEBHOOK_URL \
  payload_json='{"username": "test", "content": "hello"}' \
  file1@cat.jpg \
  file2@images/dog.jpg

Spotify

If new saved track, then make a web request

{
  "embeds": [{
    "color": 2021216,
    "title": "New song added!",
    "thumbnail": {
      "url": "{{AlbumCoverURL}}"
    },
    "fields":[
      {
        "name": "Track",
        "value": "[{{TrackName}}]({{TrackURL}})",
        "inline": true
      },
      {
        "name": "Artist",
        "value": "{{ArtistName}}",
        "inline": true
      },
      {
        "name": "Album",
        "value": "{{AlbumName}}",
        "inline": true
      }
    ],
    "footer": {
      "text": "Added {{SavedAt}}",
      "icon_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Spotify_logo_without_text.svg/200px-Spotify_logo_without_text.svg.png"
    }
  }]
}

spotify example

Twitch

Twitch embed

{
  "content": "{{ChannelUrl}}"
}

Twitch example

Custom embed

Preview will be cached and embeds will show same image.

{
  "embeds": [{
    "color": 9520895,
    "author": {
      "name": "{{ChannelName}} is now streaming",
      "url": "{{ChannelUrl}}",
      "icon_url": "https://avatar-resolver.vercel.app/twitch/{{ChannelName}}"
    },
    "fields": [
      {
        "name": ":joystick: Game",
        "value": "<<<{{Game}}>>>\u200B",
        "inline": true
      },
      {
        "name": ":busts_in_silhouette: Viewers",
        "value": "{{CurrentViewers}}",
        "inline": true
      }
    ],
    "image": { "url": "{{StreamPreview}}" }
  }]
}

Twitch example

Filter code

Fixed issue with cached preview, added game box art.

const body: any = {
  embeds: [{
    color: 0x9146ff,
    author: {
      name: `${Trigger.ChannelName} is now streaming`,
      url: Trigger.ChannelUrl,
      icon_url: `https://avatar-resolver.vercel.app/twitch/${Trigger.ChannelName}`
    },
    fields: [{
      name: ':joystick: Game',
      value: Trigger.Game || 'No Game',
      inline: true
    }, {
      name: ':busts_in_silhouette: Viewers',
      value: Trigger.CurrentViewers,
      inline: true
    }],
    thumbnail: { url: `https://avatar-resolver.vercel.app/twitch-boxart/${encodeURIComponent(Trigger.Game || '')}` },
    image: { url: `${Trigger.StreamPreview}?${+moment()}` },
    timestamp: Meta.triggerTime
  }]
};
MakerWebhooks.makeWebRequest.setBody(JSON.stringify(body));

Twitch example

Reddit

Tip:

To filter out text posts it's recommended to use New post from search trigger and subreddit:XYZ self:no query, where XYZ is subreddit name.

Reddit embed

{
  "content": "{{PostURL}}"
}

Reddit embed example

Embed with text

Image preview won't be shown and if content has more than 2048 characters, request will fail.

{
  "embeds": [{
    "color": 16729344,
    "author": {
      "name": "u/{{Author}}",
      "url": "https://www.reddit.com/user/{{Author}}",
      "icon_url": "https://avatar-resolver.vercel.app/reddit/{{Author}}"
    },
    "title": "<<<{{Title}}>>>",
    "url": "{{PostURL}}",
    "description": "<<<{{Content}}>>>",
    "footer": { "text": "r/{{Subreddit}} • Posted at {{PostedAt}}" }
  }]
}

Embed with text example

Embed with image

"file not found" placeholder will be shown on text posts.

{
  "embeds": [{
    "color": 16729344,
    "author": {
      "name": "u/{{Author}}",
      "url": "https://www.reddit.com/user/{{Author}}",
      "icon_url": "https://avatar-resolver.vercel.app/reddit/{{Author}}"
    },
    "title": "<<<{{Title}}>>>",
    "url": "{{PostURL}}",
    "image": { "url": "{{ImageURL}}" },
    "footer": { "text": "r/{{Subreddit}} • Posted at {{PostedAt}}" }
  }]
}

Embed with image example

Best with link post which are video or GIFs. "allowed_mentions" will disable everyone/here mentions in content if there any, user/role mentions will still work if you wish to add any. big text posts (over 1k characters).

{
  "content": "New Post in **r/{{Subreddit}}** by **u/{{Author}}**\n**<<<{{Title}}>>>**\n<<<{{Content}}>>>",
  "allowed_mentions": { "parse": ["users", "roles"] }
}

Link embed example

Filter code

Adds text to embed for text posts and image for link ones, shortens content so it doesn't break the body

const body: any = {
  embeds: [{
    color: 0xFF4500,
    author: {
      name: `u/${Trigger.Author}`,
      url: `https://www.reddit.com/user/${Trigger.Author}`,
      icon_url: `https://avatar-resolver.vercel.app/reddit/${Trigger.Author}`
    },
    title: Trigger.Title,
    url: Trigger.PostURL,
    footer: { text: `r/${Trigger.Subreddit}` },
    timestamp: Meta.triggerTime // returns null | use .currentUserTime for now
  }]
};
if (/^http\S+$/.test(Trigger.Content)) {
  body.embeds[0].image = { url: Trigger.ImageURL };
} else {
  body.embeds[0].description = Trigger.Content.slice(0, 2048);
}
MakerWebhooks.makeWebRequest.setBody(JSON.stringify(body));

Filter code image example

Filter code text example

Twitter

Twitter embed

{
  "content": "{{LinkToTweet}}"
}

Twitter embed example

Custom embed

Custom embed won't show image or video.

{
  "embeds": [{
    "color": 1942002,
    "author": {
      "name": "{{UserName}}",
      "url": "https://twitter.com/{{UserName}}",
      "icon_url": "https://avatar-resolver.vercel.app/twitter/{{UserName}}"
    },
    "title": "Link",
    "url": "{{LinkToTweet}}",
    "description": "<<<{{Text}}>>>",
    "footer": {
      "text": "Posted at {{CreatedAt}}"
    }
  }]
}

custom embed example

YouTube

YouTube embed

{
  "content": "{{Url}}"
}

YouTube example

Custom embed

Embed includes thumbnail without ability to watch it in Discord.

{
  "embeds": [{
    "color": 16711680,
    "author": {
      "name": "<<<{{ChannelName}}>>>",
      "icon_url": "https://avatar-resolver.vercel.app/youtube-avatar/q?url={{Url}}"
    },
    "title": "<<<{{Title}}>>>",
    "url": "{{Url}}",
    "description": "<<<{{Description}}>>>",
    "image": { "url": "https://avatar-resolver.vercel.app/youtube-thumbnail/q?url={{Url}}" },
    "footer": { "text": "Published at {{PublishedAt}}" }
  }]
}

YouTube example

Edit Webhook Message

Identically to sending messages with webhooks you can edit previously sent ones. For that you need to have message id which can be copied with right-clicking on the message and selecting Copy Message ID from context menu (Developer Mode has to be enabled in user settings). Alternatively, id can be retrieved from response after webhook sending request (wait=true has to be present in query).

  • Request URL is https://discord.com/api/webhooks/123/w3bh00k_t0k3n/messages/456, where 456 is message id.

  • Request verb has to be set to PATCH instead of POST.

  • Request body is identical to the one we use for sending, but you have to consider next things:

    • Not providing content and embeds won't remove them from message, for that you have to pass empty string ("content": "") and array ("embeds": []) respectively.
    • Attachments will be appended instead of replaced, to remove all attachments pass attachments with empty array in value ("attachments": []). If you want some of them to persist provide them in the same attachments array (attachment objects have to be retrieved from previous edit or send responses)

Discord API reference

Rate Limits

Rate limits, or on other words limitation of request rate, allow to limit usage os specific APIs. In case you've been using IFTTT you'll likely encountered rate limit errors as updates from your triggers are sent in bulk with no rate limit handling.

If to sum it up you can send 5 requests per 2 seconds per webhook, failed requests count towards rate limit same as successful ones.

If you're writing or making some thing that would require sending big number of webhook messages you have to implement proper rate limit handling. All webhook links have separate webhooks, so if you have multiple created they won't affect each others. Thankfully, request responses include all needed headers:

  • X-RateLimit-Limit - number of requests that can be made during single rate limit window
  • X-RateLimit-Remaining - number of remaining requests that can be made (decreases with each request, when it reaches zero next request will likely fail)
  • X-RateLimit-Reset - Epoch time (seconds since 00:00:00 UTC on January 1, 1970) at which the rate limit resets
  • X-RateLimit-Reset-After - Total time (in seconds) of when the current rate limit bucket will reset. Can have decimals to match previous millisecond rate limit precision
  • X-RateLimit-Bucket - A unique string denoting the rate limit being encountered (non-inclusive of top-level resources in the path)

So generally you have to save and check these values before sending new requests so your app won't run into rate limit issues.

Another anti-request fail measure would be retrying request in case it failed, but still relying on response headers.

So usual implementation should be queue to which requests for sending are being added, before each request we have to check response headers, if there's still space for another request (X-RateLimit-Remaining is greater than zero) perform one, otherwise wait X-RateLimit-Reset-After seconds or until after X-RateLimit-Reset time.

Discord API reference

Discord Markdown

Text formatting

discord markdown example

Markdown Text 101 from Discord Support. Formatting works in all fields except username, embed's title, footer's text, that means :emoji: won't work there as well (except it's in Unicode).

Discord tags

2 ways:

  1. Using developer mode: User settingsAdvancedDeveloper Mode ➤ enable. Now you can find id of any user, message, channel or server with right click ➤ Copy ID
  2. Using magic backslash \ escaping: Put it before (or if you're on iOS, surround with ``) user mention, mentionable role, channel tag or custom emoji and you will get unformatted data

discord tags example

This thing must be mentioned somewhere because some of you want to use this in messages.

How it writesHow it looks
<@&role-id>@role-name
<#channel-id>#channel-name
<@user-id>@mention
<:custom_emoji_name:emoji-id>:custom_emoji_name:

Discord API reference for available tags

Discord timestamp

Discord added support of dynamic timestamps that allow you to reference specific date and time so it will show dynamically for everyone depending on timezone they're in and Discord language setting. You can generate them by hand (need Unix timestamp) or using one of these websites:

Result string should look like this: <t:1577836800> or <t:1577836800:t>

Discord API reference for available formatting styles

Slack formatting

Discord webhooks support Slack formatting too. Just append /slack to webhook url to start using it.

First layer

DiscordSlackComment
usernameusername
avatar_urlicon_url
contenttext
embedsattachments

embeds (attachments)

DiscordSlackComment
colorcolorsupports hex codes "#4c73c7" and has three predefined colors: good(green), warning(yellow) and danger(red).
author-author block declares in different way. See the author table.
titletitle
urltitle_link
descriptiontext
fieldsfields
image-image block declares in different way. See the image table.
thumbnail-thumbnail block declares in different way. See the thumbnail table.
footerfooternot a block. See the footer table
timestamptsrequires unix timestamp format.
-fallbackembed summary for notifications. Not sure if Discord supports that.
-pretextkinda broken atm. Should works as "content" before embed, but it just appends to "description".

author

DiscordSlackComment
nameauthor_namejust write them inside of attachments like color or etc.
urlauthor_link
icon_urlauthor_icon

fields

DiscordSlackComment
nametitle
valuevalue
inlineshort

image

DiscordSlackComment
urlimage_urlno image block. Just write them inside of attachments like color or etc.

thumbnail

DiscordSlackComment
urlthumb_urlno thumbnail block. Just write them inside of attachments like color or etc.
DiscordSlackComment
textfooterno footer block. Just write them inside of attachments like color or etc.
iconfooter_icon

Example

Slack formatted example from here:

{
  "username": "Webhook",
  "icon_url": "https://i.imgur.com/4M34hi2.png",
  "text": "Text message. Up to 2000 characters.",
  "attachments": [
    {
      "author_name": "Birdie♫",
      "author_link": "https://www.reddit.com/r/cats/",
      "author_icon": "https://i.imgur.com/R66g1Pe.jpg",
      "title": "Title",
      "title_link": "https://google.com/",
      "text": "Text message. You can use Markdown here. *Italic* **bold** __underline__ ~~strikeout~~ [hyperlink](https://google.com) `code`",
      "color": "#e8d44f",
      "fields": [
        {
          "title": "Text",
          "value": "More text",
          "short": true
        },
        {
          "title": "Even more text",
          "value": "Yup",
          "short": true
        },
        {
          "title": "Use `\"inline\": true` parameter, if you want to display fields in the same line.",
          "value": "okay..."
        },
        {
          "title": "Thanks!",
          "value": "You're welcome :wink:"
        }
      ],
      "thumb_url": "https://upload.wikimedia.org/wikipedia/commons/3/38/4-Nature-Wallpapers-2014-1_ukaavUI.jpg",
      "image_url": "https://upload.wikimedia.org/wikipedia/commons/5/5a/A_picture_from_China_every_day_108.jpg",
      "footer": "Woah! So cool! :smirk:",
      "footer_icon": "https://i.imgur.com/fKL31aD.jpg"
    }
  ]
}

Additional info:

Field Limits

FieldLimit
username1-80 characters
content2000 characters
embeds10 embed objects
title*256 characters
description*4096 characters
author.name*256 characters
fields25 field objects
field.name*256 characters
field.value*1024 characters
footer.text*2048 characters
file (not actual field name)10 files
sum of characters in marked fields6000 characters

Discord API reference

Epilogue

I hope this guide will help you make cool things for your server. I spent on this guide a lot of time with checking Discord API documentation and testing webhooks with different parameters. The main reason why I made this guide is there was no detailed guide about using webhooks. Existing ones describe using webhooks with known services with pre-made json body without showing how flexible settings are. Existing ones were like copy & paste and nothing more. I do not like that.

If you wanna suggest something, like Applet recipe or something fancy, please, let me know. My Discord tag is birdie0 (was Birdie♫#6017).

Also I want to say thank next people:

  • legioncabal (LEGION#0240) - grammar fixes and pre-release testing
  • woody5728 (Delta#5728) - pre-release testing
  • wolfgang1710 (WolfGang1710#6782) - pre-release testing
  • darkpro1337 (DarkPro1337#4304) - Russian translation

Thank you all, you are awesome!!!

Contributing

Repo is public now, if you have any suggestions, fixes or improvements, feel free to open issue or create pull request.

Feel free to contact me on Discord, my Discord tag is birdie0 (was Birdie♫#6017). If you have no mutual servers with me you can always find me on the r/IFTTT Discord server.

Things that are planned, but not in progress at the moment:

  • Guide in pictures (some people understand visual information better)
  • Guides for Pipedream, Zapier and Make (formerly Integromat) services
  • Guides for Advanced REST Client, including mobile clients and iOS Shortcuts
  • Common mistakes and solutions
  • More examples
  • Translations (if you want to make or made one already, please, let me know)