Prerequisites
- A destination URL that accepts HTTP requests
Setup
For a static destination, set the Webhook URL and Authorization type on the .Profile requirements
Every HTTP request needs a destination.Dynamic destination
To set the destination per recipient, choose “Dynamic Destination” and store awebhook object on the profile. Store it once with , which merges into the profile and creates it if it does not exist:
const profile = await client.profiles.create('user_123', {
profile: {
webhook: {
url: 'https://www.example.com',
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
authentication: {
mode: 'bearer',
token: 'ABCDEFG123456',
},
},
},
});
profile = client.profiles.create(
user_id="user_123",
profile={
"webhook": {
"url": "https://www.example.com",
"method": "POST",
"headers": {
"Content-Type": "application/json",
},
"authentication": {
"mode": "bearer",
"token": "ABCDEFG123456",
},
},
},
)
curl --request POST \
--url https://api.courier.com/profiles/user_123 \
--header "Authorization: Bearer $COURIER_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"profile": {
"webhook": {
"url": "https://www.example.com",
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"authentication": {
"mode": "bearer",
"token": "ABCDEFG123456"
}
}
}
}'
profile = courier.profiles.create(
"user_123",
profile: {
webhook: {
url: "https://www.example.com",
method: "POST",
headers: {
"Content-Type" => "application/json"
},
authentication: {
mode: "bearer",
token: "ABCDEFG123456"
}
}
}
)
profile, err := client.Profiles.New(
context.TODO(),
"user_123",
courier.ProfileNewParams{
Profile: map[string]any{
"webhook": map[string]any{
"url": "https://www.example.com",
"method": "POST",
"headers": map[string]any{
"Content-Type": "application/json",
},
"authentication": map[string]any{
"mode": "bearer",
"token": "ABCDEFG123456",
},
},
},
},
)
ProfileCreateParams params = ProfileCreateParams.builder()
.userId("user_123")
.profile(ProfileCreateParams.Profile.builder()
.putAdditionalProperty("webhook", JsonValue.from(java.util.Map.of(
"url", "https://www.example.com",
"method", "POST",
"headers", java.util.Map.of(
"Content-Type", "application/json"),
"authentication", java.util.Map.of(
"mode", "bearer",
"token", "ABCDEFG123456"))))
.build())
.build();
ProfileCreateResponse profile = client.profiles().create(params);
$profile = $client->profiles->create('user_123', profile: [
'webhook' => [
'url' => 'https://www.example.com',
'method' => 'POST',
'headers' => [
'Content-Type' => 'application/json',
],
'authentication' => [
'mode' => 'bearer',
'token' => 'ABCDEFG123456',
],
],
]);
ProfileCreateParams parameters = new()
{
UserID = "user_123",
Profile = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
"""
{
"webhook": {
"url": "https://www.example.com",
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"authentication": {
"mode": "bearer",
"token": "ABCDEFG123456"
}
}
}
"""
),
};
var profile = await client.Profiles.Create(parameters);
courier profiles create \
--api-key "$COURIER_API_KEY" \
--user-id user_123 \
--profile '{"webhook":{"url":"https://www.example.com","method":"POST","headers":{"Content-Type":"application/json"},"authentication":{"mode":"bearer","token":"ABCDEFG123456"}}}'
With Courier MCP, save the webhook destination https://www.example.com on user_123.
Authentication
The webhook provider supports basic and bearer authentication. Setauthentication.mode to basic or bearer and provide the credentials. The mode defaults to none.
{
"mode": "basic",
"username": "AzureDiamond",
"password": "hunter2"
}
{
"mode": "bearer",
"token": "ABCDEFG123456"
}
user_id and Courier resolves the address.
Send by user id
The call in every language, and the rest of the profile object.
Send to a recipient
- Send to user id
- Send to
webhook
Courier reads
webhook off the saved profile, so preferences apply and the value can change without touching this code.const { requestId } = await courier.send.message({
message: {
to: {
user_id: "user_123",
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
},
});
response = client.send.message(
message={
"to": {
"user_id": "user_123",
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
},
)
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"to": {
"user_id": "user_123"
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40"
}
}'
response = courier.send_.message(
message: {
to: {
user_id: "user_123"
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40"
}
)
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{
OfUserRecipient: &shared.UserRecipientParam{
UserID: courier.String("user_123"),
},
},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
},
})
SendMessageParams params = SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
.to(UserRecipient.builder().userId("user_123").build())
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.build())
.build();
SendMessageResponse response = client.send().message(params);
$response = $client->send->message(
message: [
'to' => [
'user_id' => 'user_123',
],
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
],
);
SendMessageParams parameters = new()
{
Message = new()
{
To = new UserRecipient { UserID = "user_123" },
Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"user_id": "user_123"}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40
With Courier MCP, send my template to user_123 by email.
Pass it inline instead and nothing is stored. Swap this
to object into the call on the other tab.const { requestId } = await courier.send.message({
message: {
to: {
webhook: {
url: "https://www.example.com",
},
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
},
});
response = client.send.message(
message={
"to": {
"webhook": {
"url": "https://www.example.com",
},
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
},
)
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"to": {
"webhook": {
"url": "https://www.example.com"
}
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40"
}
}'
response = courier.send_.message(
message: {
to: {
webhook: {
url: "https://www.example.com"
}
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40"
}
)
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{
OfWebhookRecipient: &shared.WebhookRecipientParam{
Webhook: shared.WebhookProfileParam{URL: "https://www.example.com"},
},
},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
},
})
client.send().message(SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
.to(WebhookRecipient.builder()
.webhook(WebhookProfile.builder()
.url("https://www.example.com")
.build())
.build())
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.build())
.build());
$response = $client->send->message(
message: [
'to' => [
'webhook' => [
'url' => 'https://www.example.com',
],
],
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
],
);
SendMessageParams parameters = new()
{
Message = new()
{
To = new WebhookRecipient
{
Webhook = new () { Url = "https://www.example.com" },
},
Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"webhook": {"url": "https://www.example.com"}}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40
With Courier MCP, send my template to the webhook at https://www.example.com.
Request payload
The webhook provider posts the data from your send request to the destination.const { requestId } = await courier.send.message({
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
email: "sarah@acme-corp.com",
phone_number: "+12025550165",
},
data: {
name: "Sarah Bennett",
location: "Gravity Falls, OR",
},
},
});
response = client.send.message(
message={
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"to": {
"email": "sarah@acme-corp.com",
"phone_number": "+12025550165",
},
"data": {
"name": "Sarah Bennett",
"location": "Gravity Falls, OR",
},
},
)
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"to": {
"email": "sarah@acme-corp.com",
"phone_number": "+12025550165"
},
"data": {
"name": "Sarah Bennett",
"location": "Gravity Falls, OR"
}
}
}'
response = courier.send_.message(
message: {
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
to: {
email: "sarah@acme-corp.com",
phone_number: "+12025550165"
},
data: {
name: "Sarah Bennett",
location: "Gravity Falls, OR"
}
}
)
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{
OfUserRecipient: &shared.UserRecipientParam{
Email: courier.String("sarah@acme-corp.com"),
PhoneNumber: courier.String("+12025550165"),
},
},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
Data: map[string]any{
"name": "Sarah Bennett",
"location": "Gravity Falls, OR",
},
},
})
SendMessageParams params = SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
.to(UserRecipient.builder().email("sarah@acme-corp.com").phoneNumber("+12025550165").build())
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.data(JsonValue.from(java.util.Map.of(
"name", "Sarah Bennett",
"location", "Gravity Falls, OR"
)))
.build())
.build();
SendMessageResponse response = client.send().message(params);
$response = $client->send->message(
message: [
'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
'to' => [
'email' => 'sarah@acme-corp.com',
'phone_number' => '+12025550165',
],
'data' => [
'name' => 'Sarah Bennett',
'location' => 'Gravity Falls, OR',
],
],
);
SendMessageParams parameters = new()
{
Message = new()
{
To = new UserRecipient { Email = "sarah@acme-corp.com", PhoneNumber = "+12025550165" },
Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
Data = new Dictionary<string, JsonElement>()
{
{ "name", JsonSerializer.SerializeToElement("Sarah Bennett") },
{ "location", JsonSerializer.SerializeToElement("Gravity Falls, OR") },
},
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"email": "sarah@acme-corp.com", "phone_number": "+12025550165"}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
--message.data '{"name": "Sarah Bennett", "location": "Gravity Falls, OR"}'
With Courier MCP, send my template through the webhook provider with this recipient's details.
Overrides
covers the two levels and which one wins. A provider override changes what Courier sends to the destination. You can overrideurl, method, headers, and body. body and headers are deep-merged, so fields you leave out are still sent. url and method are replaced outright.
const { requestId } = await courier.send.message({
message: {
to: {
user_id: "user_123",
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
providers: {
webhook: {
override: {
url: "https://www.example.com",
method: "PUT",
headers: {
"X-Custom-Header": "Hello from Courier",
},
body: {
key: "value",
},
},
},
},
},
});
response = client.send.message(
message={
"to": {
"user_id": "user_123",
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"providers": {
"webhook": {
"override": {
"url": "https://www.example.com",
"method": "PUT",
"headers": {
"X-Custom-Header": "Hello from Courier",
},
"body": {
"key": "value",
},
},
},
},
},
)
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"to": {
"user_id": "user_123"
},
"template": "nt_01kx4h2jdafq8bk9aftxak4b40",
"providers": {
"webhook": {
"override": {
"url": "https://www.example.com",
"method": "PUT",
"headers": {
"X-Custom-Header": "Hello from Courier"
},
"body": {
"key": "value"
}
}
}
}
}
}'
response = courier.send_.message(
message: {
to: {
user_id: "user_123"
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
providers: {
"webhook" => {
override: {
url: "https://www.example.com",
method: "PUT",
headers: {
"X-Custom-Header" => "Hello from Courier"
},
body: {
key: "value"
}
}
}
}
}
)
response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
Message: courier.SendMessageParamsMessage{
To: courier.SendMessageParamsMessageToUnion{
OfUserRecipient: &shared.UserRecipientParam{
UserID: courier.String("user_123"),
},
},
Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
Providers: shared.MessageProvidersParam{
"webhook": shared.MessageProvidersTypeParam{
Override: map[string]any{
"url": "https://www.example.com",
"method": "PUT",
"headers": map[string]any{
"X-Custom-Header": "Hello from Courier",
},
"body": map[string]any{
"key": "value",
},
},
},
},
},
})
SendMessageParams params = SendMessageParams.builder()
.message(SendMessageParams.Message.builder()
.to(UserRecipient.builder().userId("user_123").build())
.template("nt_01kx4h2jdafq8bk9aftxak4b40")
.providers(MessageProviders.builder()
.putAdditionalProperty("webhook", JsonValue.from(java.util.Map.of(
"override", java.util.Map.of("url", "https://www.example.com", "method", "PUT", "headers", java.util.Map.of("X-Custom-Header", "Hello from Courier"), "body", java.util.Map.of("key", "value")))))
.build())
.build())
.build();
SendMessageResponse response = client.send().message(params);
$response = $client->send->message(
message: [
'to' => [
'user_id' => 'user_123',
],
'template' => "nt_01kx4h2jdafq8bk9aftxak4b40",
'providers' => [
'webhook' => [
'override' => [
'url' => 'https://www.example.com',
'method' => 'PUT',
'headers' => [
'X-Custom-Header' => 'Hello from Courier',
],
'body' => [
'key' => 'value',
],
],
],
],
],
);
SendMessageParams parameters = new()
{
Message = new()
{
To = new UserRecipient { UserID = "user_123" },
Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
Providers = new Dictionary<string, MessageProvidersType>()
{
{
"webhook",
new()
{
Override = new Dictionary<string, JsonElement>()
{
{ "url", JsonSerializer.SerializeToElement("https://www.example.com") },
{ "method", JsonSerializer.SerializeToElement("PUT") },
{ "headers", JsonSerializer.SerializeToElement(new { X-Custom-Header = "Hello from Courier" }) },
{ "body", JsonSerializer.SerializeToElement(new { key = "value" }) },
},
}
},
},
},
};
var response = await client.Send.Message(parameters);
courier send message \
--api-key "$COURIER_API_KEY" \
--message.to '{"user_id": "user_123"}' \
--message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
--message.providers '{"webhook": {"override": {"url": "https://www.example.com", "method": "PUT", "headers": {"X-Custom-Header": "Hello from Courier"}, "body": {"key": "value"}}}}'
With Courier MCP, send my template to user_123 over the webhook provider and override the destination URL.
Provider details
webhook
routing.channels instead is supported, and sends through just this provider.
Send to a specific provider
When that is worth doing, and what you give up: failover, channel priority, and providers you add later.