---
title: "Send a Webhook to Slack | WebhookRelay"
meta:
  "og:description": "Send any webhook to Slack. Step-by-step guide to forward an incoming webhook into a Slack channel, transforming the raw payload into Slack's { text } format in flight."
  "og:title": "Send a Webhook to Slack"
  description: "Send any webhook to Slack. Step-by-step guide to forward an incoming webhook into a Slack channel, transforming the raw payload into Slack's { text } format in flight."
---

![Stripes](https://webhookrelay.com/blog/webhook-to-slack/images/stripes.svg)

# **Send a Webhook to Slack**

Send any webhook to Slack. Step-by-step guide to forward an incoming webhook into a Slack channel, transforming the raw payload into Slack's { text } format in flight.

![Send a Webhook to Slack: Transform &amp; Forward Any Payload](https://webhookrelay.com/blog/webhook-to-slack/images/blog/heroes/route.jpg)

You have a service that fires webhooks — a CI pipeline, a signup form, an uptime monitor, a payment provider, an alerting tool — and you want each event to show up in a **Slack channel**. The problem: Slack incoming webhooks don't accept arbitrary payloads. They expect a specific JSON body, and the webhook your source sends almost never matches that shape.

[Webhook Relay](https://webhookrelay.com) sits in the middle. It receives the incoming webhook at a stable public URL, **transforms** the payload into the format Slack expects, and delivers it — no glue server, no Lambda, no maintenance.

> **Build it visually first:** the free [webhook → Slack/Discord/Teams formatter](https://webhookrelay.com/blog/webhook-to-slack/webhook-message-formatter/) lets you paste a sample payload, template the Slack message, preview it live, and copy the exact transform function used below — a faster, automatable [Discohook alternative](https://webhookrelay.com/blog/webhook-to-slack/webhook-message-formatter/) for incoming webhooks.

## [How it works](#how-it-works)

The flow is a single hop with a transform in the middle:

```
Your service  ──▶  Webhook Relay  ──▶  transform to { text }  ──▶  Slack channel
 (raw webhook)      (public URL)        (serverless function)      (message appears)
```

A Slack incoming webhook expects a JSON body like this:

```
{ "text": "your message" }
```

A generic incoming webhook isn't shaped like that, so we add a small [transformation function](https://webhookrelay.com/blog/webhook-to-slack/features/transform-webhooks/) that wraps the raw body into Slack's format before delivery. That one feature is what turns Webhook Relay into a universal "anything → Slack" bridge.

## [Step 1: Create the Slack incoming webhook URL](#step-1-create-the-slack-incoming-webhook-url)

In Slack, you create an incoming webhook through a Slack app:

1. Go to [api.slack.com/apps](https://api.slack.com/apps) and click **Create New App → From scratch**. Name it and pick your workspace.
2. Open **Incoming Webhooks** and toggle it **On**.
3. Click **Add New Webhook to Workspace**, choose the channel messages should post to, and authorize it.
4. Copy the generated **Webhook URL**. It looks like:

```
https://hooks.slack.com/services/T0000/B0000/XXXXXXXX
```

Keep this URL handy — it's the destination Webhook Relay will deliver to. Each Slack incoming webhook is bound to one channel, so create one per channel you want to post to.

## [Step 2: Create a Webhook Relay public endpoint](#step-2-create-a-webhook-relay-public-endpoint)

Open [the new public destination page](https://my.webhookrelay.com/new-public-destination) and paste the Slack incoming webhook URL as the **destination**. Webhook Relay gives you back an **input URL** — a public endpoint that you'll point your source service at.

That input URL is now a stable address you can hand to any service. Everything sent to it gets forwarded toward Slack.

## [Step 3: Add a transformation function](#step-3-add-a-transformation-function)

Right now, anything you send to the input URL would be forwarded to Slack _as-is_ — and Slack would reject it, because it isn't in `{ "text": "..." }` shape. Add a [transformation](https://webhookrelay.com/blog/webhook-to-slack/features/transform-webhooks/) to fix that.

Click **Transform** on the input, open the [Functions](https://my.webhookrelay.com/functions) page, create a function "from scratch", and paste this Lua:

```
local json = require("json")

local payload = {
    text = r.RequestBody,
}

local body, err = json.encode(payload)
if err then error(err) end

r:SetRequestHeader("content-type", "application/json")
r:SetRequestMethod("POST")
r:SetRequestBody(body)
```

This takes the raw incoming body, wraps it as `{ "text": "..." }`, sets the right headers and method, and lets Webhook Relay deliver it. Slack now accepts the message and posts it to your channel.

## [Step 4: Point your source at the URL and test](#step-4-point-your-source-at-the-url-and-test)

Configure your source service (the CI tool, form, monitor, payment provider — whatever you're integrating) to send its webhook to the Webhook Relay **input URL**.

To confirm everything works before wiring up the real service, fire a quick test with `curl`:

```
curl -X POST https://my.webhookrelay.com/v1/webhooks/your-input-id \
  -d 'Deployment finished: build #482 passed on main'
```

Within a second or two, **Deployment finished: build #482 passed on main** appears in your Slack channel. If nothing shows up, open the request log in your dashboard to see exactly what was received and delivered.

## [Going further](#going-further)

Once the basic bridge works, the transform function is where the real power is.

**Build a richer message with Block Kit.** If your source sends JSON, parse it and construct a Slack `blocks` array instead of a flat string — sections, labelled fields, dividers, even buttons:

```
local json = require("json")

local event = json.decode(r.RequestBody)

local payload = {
    blocks = {
        {
            type = "section",
            text = {
                type = "mrkdwn",
                text = "*Deployment " .. event.status .. "*\n" .. event.message,
            },
        },
    },
}

local body, err = json.encode(payload)
if err then error(err) end

r:SetRequestHeader("content-type", "application/json")
r:SetRequestMethod("POST")
r:SetRequestBody(body)
```

**Filter out noise.** Not every event deserves a ping. Use [forwarding rules](https://webhookrelay.com/blog/webhook-to-slack/features/forwarding-rules/) to only deliver events that match a condition, or drop them in the function with an early `return`.

**Fan out to multiple channels.** Add more destinations so one incoming webhook lands in several Slack channels — or in Slack _and_ Discord — at the same time. Since each Slack incoming webhook targets one channel, add one destination per channel. See [forwarding to multiple destinations](https://webhookrelay.com/blog/webhook-to-slack/features/webhook-multiple-destinations/).

For a complete real-world walkthrough of this transform pattern, see [TradingView alerts to Discord and Slack](https://webhookrelay.com/blog/webhook-to-slack/blog/trading-view/), where a plain alert message is reshaped into a chat notification.

## [FAQ](#faq)

**What does Slack need to receive?** A `POST` with a JSON body of `{ "text": "your message" }` (or a `blocks` array for Block Kit) to a Slack incoming webhook URL. The transform function produces exactly that.

**Do I need to run a server?** No. Webhook Relay receives, transforms, and delivers everything in the cloud. There's nothing to host or keep online.

**How do I see the raw payload my source sends?** Send it to a free [Webhook Bin](https://webhookrelay.com/blog/webhook-to-slack/webhook-bin/) first to inspect the exact body and headers, then write your transform around that structure.

## [Get started](#get-started)

[Create a free Webhook Relay account](https://my.webhookrelay.com/register) and set up your first "anything → Slack" bridge in a few minutes. Not sure what your source sends? [Inspect the payload in a Webhook Bin](https://webhookrelay.com/blog/webhook-to-slack/webhook-bin/) first, then write the transform to match.