Generate OAuth URL for a workspace/tenant connection
Generate an OAuth URL that creates a workspace or tenant-level channel connection (Slack workspace install or MS Teams admin consent). The generated URL expires after 5 minutes.
import { Novu } from "@novu/api";
const novu = new Novu({
secretKey: "YOUR_SECRET_KEY_HERE",
});
async function run() {
const result = await novu.integrations.generateConnectOAuthUrl({
subscriberId: "subscriber-123",
integrationIdentifier: "<value>",
connectionIdentifier: "slack-connection-abc123",
context: {
"key": "org-acme",
},
scope: [
"chat:write",
"chat:write.public",
"channels:read",
],
connectionMode: "shared",
autoLinkUser: true,
});
console.log(result);
}
run();using Novu;
using Novu.Models.Components;
using System.Collections.Generic;
var sdk = new NovuSDK(secretKey: "YOUR_SECRET_KEY_HERE");
var res = await sdk.Integrations.GenerateConnectOAuthUrlAsync(generateConnectOauthUrlRequestDto: new GenerateConnectOauthUrlRequestDto() {
SubscriberId = "subscriber-123",
IntegrationIdentifier = "<value>",
ConnectionIdentifier = "slack-connection-abc123",
Context = new Dictionary<string, GenerateConnectOauthUrlRequestDtoContext>() {
{ "key", GenerateConnectOauthUrlRequestDtoContext.CreateStr(
"org-acme"
) },
},
Scope = new List<string>() {
"chat:write",
"chat:write.public",
"channels:read",
},
ConnectionMode = GenerateConnectOauthUrlRequestDtoConnectionMode.Shared,
AutoLinkUser = true,
});
// handle responsedeclare(strict_types=1);
require 'vendor/autoload.php';
use novu;
use novu\Models\Components;
$sdk = novu\Novu::builder()
->setSecurity(
'YOUR_SECRET_KEY_HERE'
)
->build();
$generateConnectOauthUrlRequestDto = new Components\GenerateConnectOauthUrlRequestDto(
subscriberId: 'subscriber-123',
integrationIdentifier: '<value>',
connectionIdentifier: 'slack-connection-abc123',
context: [
'key' => 'org-acme',
],
scope: [
'chat:write',
'chat:write.public',
'channels:read',
],
connectionMode: Components\GenerateConnectOauthUrlRequestDtoConnectionMode::Shared,
autoLinkUser: true,
);
$response = $sdk->integrations->generateConnectOAuthUrl(
generateConnectOauthUrlRequestDto: $generateConnectOauthUrlRequestDto
);
if ($response->generateChatOAuthUrlResponseDto !== null) {
// handle response
}import novu_py
from novu_py import Novu
with Novu(
secret_key="YOUR_SECRET_KEY_HERE",
) as novu:
res = novu.integrations.generate_connect_o_auth_url(generate_connect_oauth_url_request_dto={
"subscriber_id": "subscriber-123",
"integration_identifier": "<value>",
"connection_identifier": "slack-connection-abc123",
"context": {
"key": "org-acme",
},
"scope": [
"chat:write",
"chat:write.public",
"channels:read",
],
"connection_mode": novu_py.GenerateConnectOauthURLRequestDtoConnectionMode.SHARED,
"auto_link_user": True,
})
# Handle response
print(res)package main
import(
"context"
"github.com/novuhq/novu-go/v3"
"github.com/novuhq/novu-go/v3/models/components"
"log"
)
func main() {
ctx := context.Background()
s := v3.New(
v3.WithSecurity("YOUR_SECRET_KEY_HERE"),
)
res, err := s.Integrations.GenerateConnectOAuthURL(ctx, components.GenerateConnectOauthURLRequestDto{
SubscriberID: v3.Pointer("subscriber-123"),
IntegrationIdentifier: "<value>",
ConnectionIdentifier: v3.Pointer("slack-connection-abc123"),
Context: map[string]components.GenerateConnectOauthURLRequestDtoContext{
"key": components.CreateGenerateConnectOauthURLRequestDtoContextStr(
"org-acme",
),
},
Scope: []string{
"chat:write",
"chat:write.public",
"channels:read",
},
ConnectionMode: components.GenerateConnectOauthURLRequestDtoConnectionModeShared.ToPointer(),
AutoLinkUser: v3.Pointer(true),
}, nil)
if err != nil {
log.Fatal(err)
}
if res.GenerateChatOAuthURLResponseDto != nil {
// handle response
}
}curl --request POST \
--url https://api.novu.co/v1/integrations/channel-connections/oauth \
--header 'Authorization: <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"integrationIdentifier": "<string>",
"subscriberId": "subscriber-123",
"connectionIdentifier": "slack-connection-abc123",
"context": {},
"scope": [
"chat:write",
"chat:write.public",
"channels:read"
],
"connectionMode": "shared",
"autoLinkUser": true
}
'const options = {
method: 'POST',
headers: {Authorization: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
integrationIdentifier: '<string>',
subscriberId: 'subscriber-123',
connectionIdentifier: 'slack-connection-abc123',
context: {},
scope: ['chat:write', 'chat:write.public', 'channels:read'],
connectionMode: 'shared',
autoLinkUser: true
})
};
fetch('https://api.novu.co/v1/integrations/channel-connections/oauth', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));HttpResponse<String> response = Unirest.post("https://api.novu.co/v1/integrations/channel-connections/oauth")
.header("Authorization", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"integrationIdentifier\": \"<string>\",\n \"subscriberId\": \"subscriber-123\",\n \"connectionIdentifier\": \"slack-connection-abc123\",\n \"context\": {},\n \"scope\": [\n \"chat:write\",\n \"chat:write.public\",\n \"channels:read\"\n ],\n \"connectionMode\": \"shared\",\n \"autoLinkUser\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.novu.co/v1/integrations/channel-connections/oauth")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"integrationIdentifier\": \"<string>\",\n \"subscriberId\": \"subscriber-123\",\n \"connectionIdentifier\": \"slack-connection-abc123\",\n \"context\": {},\n \"scope\": [\n \"chat:write\",\n \"chat:write.public\",\n \"channels:read\"\n ],\n \"connectionMode\": \"shared\",\n \"autoLinkUser\": true\n}"
response = http.request(request)
puts response.read_body{
"url": "https://slack.com/oauth/v2/authorize?state=..."
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"errors": {
"fieldName1": {
"messages": [
"Field is required",
"Must be a valid email address"
],
"value": "invalidEmail"
},
"fieldName2": {
"messages": [
"Must be at least 18 years old"
],
"value": 17
},
"fieldName3": {
"messages": [
"Must be a boolean value"
],
"value": true
},
"fieldName4": {
"messages": [
"Must be a valid object"
],
"value": {
"key": "value"
}
},
"fieldName5": {
"messages": [
"Field is missing"
],
"value": null
},
"fieldName6": {
"messages": [
"Undefined value"
]
}
},
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}"API rate limit exceeded"{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}"Please wait some time, then try again."Authorizations
API key authentication. Allowed headers-- "Authorization: ApiKey <novu_secret_key>".
Headers
A header for idempotency purposes
Body
Integration identifier
The subscriber ID to associate with the channel connection. For Slack: optional for workspace connections (required only for incoming-webhook scope). For MS Teams: optional. Admin consent is tenant-wide.
"subscriber-123"
Identifier of the channel connection that will be created. Generated automatically if not provided.
"slack-connection-abc123"
Show child attributes
Show child attributes
Slack only: OAuth scopes to request during authorization. If not specified, default scopes will be used: chat:write, chat:write.public, channels:read, groups:read, users:read, users:read.email. MS Teams: ignored — uses admin consent with pre-configured Azure AD permissions.
[
"chat:write",
"chat:write.public",
"channels:read"
]
Connection mode that determines how the channel connection is scoped. "subscriber" (default) associates the connection with a specific subscriber. "shared" associates the connection with a context instead of a subscriber.
subscriber, shared "shared"
When true (default when connectionMode is "subscriber"), after the workspace/tenant connection is created the OAuth flow also links the subscriber who clicked "Connect" as a personal endpoint. For Slack, uses the authed_user.id returned by oauth.v2.access — no extra redirect. For MS Teams, triggers a second OAuth redirect for delegated user-identity consent. Set to false to only create the workspace connection without linking the individual user.
true
Response
Created
The OAuth authorization URL for the chat provider. For Slack: https://slack.com/oauth/v2/authorize?... For MS Teams: https://login.microsoftonline.com/.../adminconsent?... This URL should be presented to the user to authorize the integration. Expires after 5 minutes.
"https://slack.com/oauth/v2/authorize?state=..."
Was this page helpful?
import { Novu } from "@novu/api";
const novu = new Novu({
secretKey: "YOUR_SECRET_KEY_HERE",
});
async function run() {
const result = await novu.integrations.generateConnectOAuthUrl({
subscriberId: "subscriber-123",
integrationIdentifier: "<value>",
connectionIdentifier: "slack-connection-abc123",
context: {
"key": "org-acme",
},
scope: [
"chat:write",
"chat:write.public",
"channels:read",
],
connectionMode: "shared",
autoLinkUser: true,
});
console.log(result);
}
run();using Novu;
using Novu.Models.Components;
using System.Collections.Generic;
var sdk = new NovuSDK(secretKey: "YOUR_SECRET_KEY_HERE");
var res = await sdk.Integrations.GenerateConnectOAuthUrlAsync(generateConnectOauthUrlRequestDto: new GenerateConnectOauthUrlRequestDto() {
SubscriberId = "subscriber-123",
IntegrationIdentifier = "<value>",
ConnectionIdentifier = "slack-connection-abc123",
Context = new Dictionary<string, GenerateConnectOauthUrlRequestDtoContext>() {
{ "key", GenerateConnectOauthUrlRequestDtoContext.CreateStr(
"org-acme"
) },
},
Scope = new List<string>() {
"chat:write",
"chat:write.public",
"channels:read",
},
ConnectionMode = GenerateConnectOauthUrlRequestDtoConnectionMode.Shared,
AutoLinkUser = true,
});
// handle responsedeclare(strict_types=1);
require 'vendor/autoload.php';
use novu;
use novu\Models\Components;
$sdk = novu\Novu::builder()
->setSecurity(
'YOUR_SECRET_KEY_HERE'
)
->build();
$generateConnectOauthUrlRequestDto = new Components\GenerateConnectOauthUrlRequestDto(
subscriberId: 'subscriber-123',
integrationIdentifier: '<value>',
connectionIdentifier: 'slack-connection-abc123',
context: [
'key' => 'org-acme',
],
scope: [
'chat:write',
'chat:write.public',
'channels:read',
],
connectionMode: Components\GenerateConnectOauthUrlRequestDtoConnectionMode::Shared,
autoLinkUser: true,
);
$response = $sdk->integrations->generateConnectOAuthUrl(
generateConnectOauthUrlRequestDto: $generateConnectOauthUrlRequestDto
);
if ($response->generateChatOAuthUrlResponseDto !== null) {
// handle response
}import novu_py
from novu_py import Novu
with Novu(
secret_key="YOUR_SECRET_KEY_HERE",
) as novu:
res = novu.integrations.generate_connect_o_auth_url(generate_connect_oauth_url_request_dto={
"subscriber_id": "subscriber-123",
"integration_identifier": "<value>",
"connection_identifier": "slack-connection-abc123",
"context": {
"key": "org-acme",
},
"scope": [
"chat:write",
"chat:write.public",
"channels:read",
],
"connection_mode": novu_py.GenerateConnectOauthURLRequestDtoConnectionMode.SHARED,
"auto_link_user": True,
})
# Handle response
print(res)package main
import(
"context"
"github.com/novuhq/novu-go/v3"
"github.com/novuhq/novu-go/v3/models/components"
"log"
)
func main() {
ctx := context.Background()
s := v3.New(
v3.WithSecurity("YOUR_SECRET_KEY_HERE"),
)
res, err := s.Integrations.GenerateConnectOAuthURL(ctx, components.GenerateConnectOauthURLRequestDto{
SubscriberID: v3.Pointer("subscriber-123"),
IntegrationIdentifier: "<value>",
ConnectionIdentifier: v3.Pointer("slack-connection-abc123"),
Context: map[string]components.GenerateConnectOauthURLRequestDtoContext{
"key": components.CreateGenerateConnectOauthURLRequestDtoContextStr(
"org-acme",
),
},
Scope: []string{
"chat:write",
"chat:write.public",
"channels:read",
},
ConnectionMode: components.GenerateConnectOauthURLRequestDtoConnectionModeShared.ToPointer(),
AutoLinkUser: v3.Pointer(true),
}, nil)
if err != nil {
log.Fatal(err)
}
if res.GenerateChatOAuthURLResponseDto != nil {
// handle response
}
}curl --request POST \
--url https://api.novu.co/v1/integrations/channel-connections/oauth \
--header 'Authorization: <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"integrationIdentifier": "<string>",
"subscriberId": "subscriber-123",
"connectionIdentifier": "slack-connection-abc123",
"context": {},
"scope": [
"chat:write",
"chat:write.public",
"channels:read"
],
"connectionMode": "shared",
"autoLinkUser": true
}
'const options = {
method: 'POST',
headers: {Authorization: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
integrationIdentifier: '<string>',
subscriberId: 'subscriber-123',
connectionIdentifier: 'slack-connection-abc123',
context: {},
scope: ['chat:write', 'chat:write.public', 'channels:read'],
connectionMode: 'shared',
autoLinkUser: true
})
};
fetch('https://api.novu.co/v1/integrations/channel-connections/oauth', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));HttpResponse<String> response = Unirest.post("https://api.novu.co/v1/integrations/channel-connections/oauth")
.header("Authorization", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"integrationIdentifier\": \"<string>\",\n \"subscriberId\": \"subscriber-123\",\n \"connectionIdentifier\": \"slack-connection-abc123\",\n \"context\": {},\n \"scope\": [\n \"chat:write\",\n \"chat:write.public\",\n \"channels:read\"\n ],\n \"connectionMode\": \"shared\",\n \"autoLinkUser\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.novu.co/v1/integrations/channel-connections/oauth")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"integrationIdentifier\": \"<string>\",\n \"subscriberId\": \"subscriber-123\",\n \"connectionIdentifier\": \"slack-connection-abc123\",\n \"context\": {},\n \"scope\": [\n \"chat:write\",\n \"chat:write.public\",\n \"channels:read\"\n ],\n \"connectionMode\": \"shared\",\n \"autoLinkUser\": true\n}"
response = http.request(request)
puts response.read_body{
"url": "https://slack.com/oauth/v2/authorize?state=..."
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"errors": {
"fieldName1": {
"messages": [
"Field is required",
"Must be a valid email address"
],
"value": "invalidEmail"
},
"fieldName2": {
"messages": [
"Must be at least 18 years old"
],
"value": 17
},
"fieldName3": {
"messages": [
"Must be a boolean value"
],
"value": true
},
"fieldName4": {
"messages": [
"Must be a valid object"
],
"value": {
"key": "value"
}
},
"fieldName5": {
"messages": [
"Field is missing"
],
"value": null
},
"fieldName6": {
"messages": [
"Undefined value"
]
}
},
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}"API rate limit exceeded"{
"statusCode": 404,
"timestamp": "2024-12-12T13:00:00Z",
"path": "/api/v1/resource",
"message": "xx xx xx ",
"ctx": {
"workflowId": "some_wf_id",
"stepId": "some_wf_id"
},
"errorId": "abc123"
}"Please wait some time, then try again."