RelayAPI

Discord API

Schedule and automate Discord messages with RelayAPI — text messages, image embeds, video links, custom usernames, avatars, and rich embeds via webhooks.

Quick Reference

PropertyValue
Platform keydiscord
Auth methodWebhook URL
Text limit2,000 characters
Images per message10 (as embeds)
Videos per messageAppended as links (auto-embedded by Discord)
Image formatsJPEG, PNG, GIF, WebP
Image max size25 MB
Video max size25 MB
Message typesText, Image Embeds, Video Links, Rich Embeds
SchedulingYes
AnalyticsNo

SDK sourceTypeScript · Python · Go · Java · REST API

Before You Start

Discord integration uses webhook URLs — no OAuth needed. The webhook URL itself contains the authentication token. Create a webhook in your Discord server's channel settings (Edit Channel > Integrations > Webhooks). Images are sent as embeds (up to 10 per message). Videos are appended to the message content as URLs and auto-embedded by Discord. You can customize the username and avatar per message.

Quick Start

Send a message to a Discord channel:

import Relay from '@relayapi/sdk';

const client = new Relay();
const post = await client.posts.create({
  content: 'Hello from RelayAPI!',
  targets: ['discord'],
  scheduled_at: 'now',
});

console.log(post.id); // post_abc123
from relay import Relay

client = Relay()
post = client.posts.create(
    content='Hello from RelayAPI!',
    targets=['discord'],
    scheduled_at='now',
)

print(post.id)  # post_abc123
client := relaygo.NewClient()

post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Hello from RelayAPI!"),
    Targets:     relaygo.F([]string{"discord"}),
    ScheduledAt: relaygo.F("now"),
})

fmt.Println(post.ID) // post_abc123
RelayClient client = RelayOkHttpClient.fromEnv();

PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Hello from RelayAPI!")
    .addTarget("discord")
    .scheduledAt("now")
    .build());

System.out.println(post.id()); // post_abc123
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Hello from RelayAPI!",
    "targets": ["discord"],
    "scheduled_at": "now"
  }'

Content Types

Text Message

Plain text message up to 2,000 characters.

const post = await client.posts.create({
  content: 'Hello from RelayAPI!',
  targets: ['discord'],
  scheduled_at: 'now',
});
post = client.posts.create(
    content='Hello from RelayAPI!',
    targets=['discord'],
    scheduled_at='now',
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Hello from RelayAPI!"),
    Targets:     relaygo.F([]string{"discord"}),
    ScheduledAt: relaygo.F("now"),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Hello from RelayAPI!")
    .addTarget("discord")
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Hello from RelayAPI!",
    "targets": ["discord"],
    "scheduled_at": "now"
  }'

Message with Custom Username and Avatar

Override the webhook's default name and avatar for a specific message.

const post = await client.posts.create({
  content: 'Server maintenance starting in 30 minutes.',
  targets: ['discord'],
  scheduled_at: 'now',
  target_options: {
    discord: {
      username: 'Maintenance Bot',
      avatar_url: 'https://cdn.example.com/bot-avatar.png',
    },
  },
});
post = client.posts.create(
    content='Server maintenance starting in 30 minutes.',
    targets=['discord'],
    scheduled_at='now',
    target_options={
        'discord': {
            'username': 'Maintenance Bot',
            'avatar_url': 'https://cdn.example.com/bot-avatar.png',
        },
    },
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Server maintenance starting in 30 minutes."),
    Targets:     relaygo.F([]string{"discord"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "discord": map[string]interface{}{
            "username":   "Maintenance Bot",
            "avatar_url": "https://cdn.example.com/bot-avatar.png",
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Server maintenance starting in 30 minutes.")
    .addTarget("discord")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("discord", JsonValue.from(Map.of(
            "username", "Maintenance Bot",
            "avatar_url", "https://cdn.example.com/bot-avatar.png"
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Server maintenance starting in 30 minutes.",
    "targets": ["discord"],
    "scheduled_at": "now",
    "target_options": {
      "discord": {
        "username": "Maintenance Bot",
        "avatar_url": "https://cdn.example.com/bot-avatar.png"
      }
    }
  }'

Image Message

Images are sent as embeds. Up to 10 images per message.

const post = await client.posts.create({
  content: 'Check out these screenshots!',
  targets: ['discord'],
  media: [
    { url: 'https://cdn.example.com/screenshot1.png', type: 'image' },
    { url: 'https://cdn.example.com/screenshot2.png', type: 'image' },
  ],
  scheduled_at: 'now',
});
post = client.posts.create(
    content='Check out these screenshots!',
    targets=['discord'],
    media=[
        {'url': 'https://cdn.example.com/screenshot1.png', 'type': 'image'},
        {'url': 'https://cdn.example.com/screenshot2.png', 'type': 'image'},
    ],
    scheduled_at='now',
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content: relaygo.F("Check out these screenshots!"),
    Targets: relaygo.F([]string{"discord"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{
        {URL: relaygo.F("https://cdn.example.com/screenshot1.png"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage)},
        {URL: relaygo.F("https://cdn.example.com/screenshot2.png"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage)},
    }),
    ScheduledAt: relaygo.F("now"),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Check out these screenshots!")
    .addTarget("discord")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/screenshot1.png")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/screenshot2.png")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Check out these screenshots!",
    "targets": ["discord"],
    "media": [
      {"url": "https://cdn.example.com/screenshot1.png", "type": "image"},
      {"url": "https://cdn.example.com/screenshot2.png", "type": "image"}
    ],
    "scheduled_at": "now"
  }'

Video Message

Videos are appended as URLs in the message content. Discord auto-embeds supported video links.

const post = await client.posts.create({
  content: 'Watch our latest update!',
  targets: ['discord'],
  media: [
    { url: 'https://cdn.example.com/video.mp4', type: 'video' },
  ],
  scheduled_at: 'now',
});
post = client.posts.create(
    content='Watch our latest update!',
    targets=['discord'],
    media=[
        {'url': 'https://cdn.example.com/video.mp4', 'type': 'video'},
    ],
    scheduled_at='now',
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content: relaygo.F("Watch our latest update!"),
    Targets: relaygo.F([]string{"discord"}),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{
        {URL: relaygo.F("https://cdn.example.com/video.mp4"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeVideo)},
    }),
    ScheduledAt: relaygo.F("now"),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Watch our latest update!")
    .addTarget("discord")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/video.mp4")
        .type(PostCreateParams.Media.Type.VIDEO)
        .build())
    .scheduledAt("now")
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Watch our latest update!",
    "targets": ["discord"],
    "media": [
      {"url": "https://cdn.example.com/video.mp4", "type": "video"}
    ],
    "scheduled_at": "now"
  }'

Rich Embeds

Send structured rich embeds with titles, descriptions, colors, images, and more.

const post = await client.posts.create({
  targets: ['discord'],
  scheduled_at: 'now',
  target_options: {
    discord: {
      embeds: [
        {
          title: 'New Release: v2.0',
          description: 'We just shipped a major update with new features!',
          url: 'https://example.com/changelog',
          color: 5814783,
          image: { url: 'https://cdn.example.com/banner.png' },
          footer: { text: 'RelayAPI' },
          timestamp: '2026-04-01T12:00:00Z',
        },
      ],
    },
  },
});
post = client.posts.create(
    targets=['discord'],
    scheduled_at='now',
    target_options={
        'discord': {
            'embeds': [
                {
                    'title': 'New Release: v2.0',
                    'description': 'We just shipped a major update with new features!',
                    'url': 'https://example.com/changelog',
                    'color': 5814783,
                    'image': {'url': 'https://cdn.example.com/banner.png'},
                    'footer': {'text': 'RelayAPI'},
                    'timestamp': '2026-04-01T12:00:00Z',
                },
            ],
        },
    },
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Targets:     relaygo.F([]string{"discord"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "discord": map[string]interface{}{
            "embeds": []map[string]interface{}{
                {
                    "title":       "New Release: v2.0",
                    "description": "We just shipped a major update with new features!",
                    "url":         "https://example.com/changelog",
                    "color":       5814783,
                    "image":       map[string]interface{}{"url": "https://cdn.example.com/banner.png"},
                    "footer":      map[string]interface{}{"text": "RelayAPI"},
                    "timestamp":   "2026-04-01T12:00:00Z",
                },
            },
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .addTarget("discord")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("discord", JsonValue.from(Map.of(
            "embeds", List.of(Map.of(
                "title", "New Release: v2.0",
                "description", "We just shipped a major update with new features!",
                "url", "https://example.com/changelog",
                "color", 5814783,
                "image", Map.of("url", "https://cdn.example.com/banner.png"),
                "footer", Map.of("text", "RelayAPI"),
                "timestamp", "2026-04-01T12:00:00Z"
            ))
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "targets": ["discord"],
    "scheduled_at": "now",
    "target_options": {
      "discord": {
        "embeds": [
          {
            "title": "New Release: v2.0",
            "description": "We just shipped a major update with new features!",
            "url": "https://example.com/changelog",
            "color": 5814783,
            "image": {"url": "https://cdn.example.com/banner.png"},
            "footer": {"text": "RelayAPI"},
            "timestamp": "2026-04-01T12:00:00Z"
          }
        ]
      }
    }
  }'

Text with Rich Embeds

Combine regular text content with rich embeds.

const post = await client.posts.create({
  content: '📢 Announcement',
  targets: ['discord'],
  scheduled_at: 'now',
  target_options: {
    discord: {
      embeds: [
        {
          title: 'Scheduled Maintenance',
          description: 'We will be performing maintenance on April 5th from 2-4 AM UTC.',
          color: 16776960,
        },
      ],
    },
  },
});
post = client.posts.create(
    content='📢 Announcement',
    targets=['discord'],
    scheduled_at='now',
    target_options={
        'discord': {
            'embeds': [
                {
                    'title': 'Scheduled Maintenance',
                    'description': 'We will be performing maintenance on April 5th from 2-4 AM UTC.',
                    'color': 16776960,
                },
            ],
        },
    },
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("📢 Announcement"),
    Targets:     relaygo.F([]string{"discord"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "discord": map[string]interface{}{
            "embeds": []map[string]interface{}{
                {
                    "title":       "Scheduled Maintenance",
                    "description": "We will be performing maintenance on April 5th from 2-4 AM UTC.",
                    "color":       16776960,
                },
            },
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("📢 Announcement")
    .addTarget("discord")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("discord", JsonValue.from(Map.of(
            "embeds", List.of(Map.of(
                "title", "Scheduled Maintenance",
                "description", "We will be performing maintenance on April 5th from 2-4 AM UTC.",
                "color", 16776960
            ))
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "📢 Announcement",
    "targets": ["discord"],
    "scheduled_at": "now",
    "target_options": {
      "discord": {
        "embeds": [
          {
            "title": "Scheduled Maintenance",
            "description": "We will be performing maintenance on April 5th from 2-4 AM UTC.",
            "color": 16776960
          }
        ]
      }
    }
  }'

Media Requirements

Images

PropertyRequirement
Max per message10 (as embeds)
FormatsJPEG, PNG, GIF, WebP
Max file size25 MB

Videos

PropertyRequirement
Max per message1 (as URL in content)
FormatsMP4, WebM, MOV
Max file size25 MB
DeliveryAppended to message content as URL

target_options Fields

All fields go inside target_options.discord on your post request.

FieldTypeDefaultDescription
contentstringOverride message content for Discord specifically
mediaobject[]Override media for Discord specifically
usernamestringOverride the webhook's display name for this message
avatar_urlstringOverride the webhook's avatar for this message
embedsobject[]Rich embed objects (up to 10). Fields: title, description, url, color, image, thumbnail, footer, timestamp

Common Errors

ErrorCauseFix
Content too longText exceeds 2,000 charactersShorten the content. Use target_options.discord.content for a shorter version.
Invalid webhook URLWebhook URL is malformed or deletedCreate a new webhook in the Discord channel settings (Edit Channel > Integrations > Webhooks).
Empty messageNo content or embeds providedAdd text content or at least one embed.
Too many embedsMore than 10 embedsReduce to 10 or fewer embeds per message.

Known Quirks

  • No OAuth required — Discord webhooks are self-authenticating via the URL. Keep webhook URLs secret.
  • Custom username and avatar per message — each message can have a different display name and avatar.
  • Images are embeds — images are sent as embed objects, not direct attachments.
  • Videos are auto-embedded — video URLs in message content are automatically embedded by Discord if the format is supported.
  • Embed color is an integer — use decimal color values (e.g., 5814783 for #58B9FF).
  • No direct message URL — webhook responses don't include a jump URL to the message.
  • Rate limits — Discord enforces rate limits on webhooks (30 messages per 60 seconds per channel).
  • Markdown supported — Discord messages support a subset of Markdown: **bold**, *italic*, ~~strikethrough~~, `code`, > quote.

Automations

Discord automation uses the bot token as a single credential. All send actions target a channel, not a user — for DMs, pre-create the DM channel with the bot and store the channel_id as the contact's Discord identifier.

Triggers

TypeFires on
discord_messageGuild channel message
discord_dmDM to the bot
discord_reactionEmoji reaction added
discord_member_joinedNew member joins a guild
discord_thread_createdThread opened in a channel
discord_interactionSlash command / button / select interaction

Send nodes

Endpoint: POST https://discord.com/api/v10/channels/{channel-id}/messages (+ variants).

NodeRequired fields
discord_send_messagecontent, optional channel_id (else from state/contact)
discord_send_embedembeds[] (≤10)
discord_send_componentscomponents[], optional content
discord_send_attachmenturl, optional content (URL is embedded in content for unfurl)
discord_reactemoji, message_id
discord_edit_messagecontent, message_id
discord_start_threadname (≤100), optional auto_archive_duration, message_id

Found something wrong? Help us improve this page.

On this page

Submit an Issue
Requires a GitHub account.View repo