Create Webhook
curl --request POST \
--url https://api.linkupapi.com/v2/webhooks \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--data '
{
"account_id": "<string>",
"url": "<string>",
"events": [
{}
],
"retry": true,
"enable_signature": true
}
'import requests
url = "https://api.linkupapi.com/v2/webhooks"
payload = {
"account_id": "<string>",
"url": "<string>",
"events": [{}],
"retry": True,
"enable_signature": True
}
headers = {
"x-api-key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
account_id: '<string>',
url: '<string>',
events: [{}],
retry: true,
enable_signature: true
})
};
fetch('https://api.linkupapi.com/v2/webhooks', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.linkupapi.com/v2/webhooks",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'account_id' => '<string>',
'url' => '<string>',
'events' => [
[
]
],
'retry' => true,
'enable_signature' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.linkupapi.com/v2/webhooks"
payload := strings.NewReader("{\n \"account_id\": \"<string>\",\n \"url\": \"<string>\",\n \"events\": [\n {}\n ],\n \"retry\": true,\n \"enable_signature\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.linkupapi.com/v2/webhooks")
.header("x-api-key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"account_id\": \"<string>\",\n \"url\": \"<string>\",\n \"events\": [\n {}\n ],\n \"retry\": true,\n \"enable_signature\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.linkupapi.com/v2/webhooks")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"account_id\": \"<string>\",\n \"url\": \"<string>\",\n \"events\": [\n {}\n ],\n \"retry\": true,\n \"enable_signature\": true\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"webhook_id": "6789abcdef0123456789abcd",
"account_id": "69c127c37cae0494dd827286",
"mode": "hosted",
"stream_url": "/v2/webhooks/6789abcdef0123456789abcd/stream",
"events_url": "/v2/webhooks/6789abcdef0123456789abcd/events",
"events": ["accepted_invitation", "message_received"],
"is_active": true
},
"metadata": {
"action": "create_webhook",
"credits_consumed": 0,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
{
"success": true,
"data": {
"webhook_id": "6789abcdef0123456789abcd",
"account_id": "69c127c37cae0494dd827286",
"mode": "custom",
"url": "https://your-server.com/webhook",
"events": ["all"],
"is_active": true
},
"metadata": {
"action": "create_webhook",
"credits_consumed": 0,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
{
"success": false,
"error": {
"code": "INVALID_ACCOUNT",
"message": "Account not found"
},
"metadata": {
"action": "create_webhook",
"credits_consumed": 0,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
Webhooks (Real-Time)
Create Webhook
Register a webhook to receive real-time events for an account
POST
/
v2
/
webhooks
Create Webhook
curl --request POST \
--url https://api.linkupapi.com/v2/webhooks \
--header 'Content-Type: application/json' \
--header 'x-api-key: <x-api-key>' \
--data '
{
"account_id": "<string>",
"url": "<string>",
"events": [
{}
],
"retry": true,
"enable_signature": true
}
'import requests
url = "https://api.linkupapi.com/v2/webhooks"
payload = {
"account_id": "<string>",
"url": "<string>",
"events": [{}],
"retry": True,
"enable_signature": True
}
headers = {
"x-api-key": "<x-api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<x-api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
account_id: '<string>',
url: '<string>',
events: [{}],
retry: true,
enable_signature: true
})
};
fetch('https://api.linkupapi.com/v2/webhooks', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.linkupapi.com/v2/webhooks",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'account_id' => '<string>',
'url' => '<string>',
'events' => [
[
]
],
'retry' => true,
'enable_signature' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <x-api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.linkupapi.com/v2/webhooks"
payload := strings.NewReader("{\n \"account_id\": \"<string>\",\n \"url\": \"<string>\",\n \"events\": [\n {}\n ],\n \"retry\": true,\n \"enable_signature\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<x-api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.linkupapi.com/v2/webhooks")
.header("x-api-key", "<x-api-key>")
.header("Content-Type", "application/json")
.body("{\n \"account_id\": \"<string>\",\n \"url\": \"<string>\",\n \"events\": [\n {}\n ],\n \"retry\": true,\n \"enable_signature\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.linkupapi.com/v2/webhooks")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<x-api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"account_id\": \"<string>\",\n \"url\": \"<string>\",\n \"events\": [\n {}\n ],\n \"retry\": true,\n \"enable_signature\": true\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"webhook_id": "6789abcdef0123456789abcd",
"account_id": "69c127c37cae0494dd827286",
"mode": "hosted",
"stream_url": "/v2/webhooks/6789abcdef0123456789abcd/stream",
"events_url": "/v2/webhooks/6789abcdef0123456789abcd/events",
"events": ["accepted_invitation", "message_received"],
"is_active": true
},
"metadata": {
"action": "create_webhook",
"credits_consumed": 0,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
{
"success": true,
"data": {
"webhook_id": "6789abcdef0123456789abcd",
"account_id": "69c127c37cae0494dd827286",
"mode": "custom",
"url": "https://your-server.com/webhook",
"events": ["all"],
"is_active": true
},
"metadata": {
"action": "create_webhook",
"credits_consumed": 0,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
{
"success": false,
"error": {
"code": "INVALID_ACCOUNT",
"message": "Account not found"
},
"metadata": {
"action": "create_webhook",
"credits_consumed": 0,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
Create a webhook listener for one of your connected accounts. The delivery mode is determined by whether you provide a
url:
- With
url— Custom mode: events are sent asPOSTrequests to your URL - Without
url— Hosted mode: events are available via SSE stream or polling
Monitoring starts automatically. As soon as the webhook is created, the account’s real-time SSE stream is opened and credit billing begins (≈10 credits/day per account). You don’t need to call
/start — that endpoint is only used to resume a webhook that was previously stopped via /stop.Calling POST /v2/webhooks itself costs 0 credits; the running cost comes from the live monitoring it enables.Header Parameters
string
required
Your API key
Body Parameters
string
required
The ID of the account to monitor. Must be an account you own (created via
/v2/login).string
The URL where events will be sent as
POST requests. Must be a publicly accessible HTTPS endpoint.Omit this field to create a hosted webhook (SSE/polling).array
default:"[\"all\"]"
List of event types to listen for. Defaults to
["all"] which receives every event. Applies to custom mode only: hosted webhooks always receive every event type.Available event types: message_received, accepted_invitation, invitation_received, all. For an email account, message_received covers new incoming emails and email_bounced (delivered via all) covers bounces — see Email events.boolean
default:"true"
Custom mode only. When
true, a POST that times out or gets a 5xx/429 is retried with backoff (8 attempts over ~45 h) and every attempt is visible in the delivery log. Set false for fire-and-forget (failures are still logged). See Delivery & retries.boolean
default:"false"
Custom mode only. When
true, every outbound POST is signed with HMAC-SHA256 so you can verify it came from LinkUp. The response returns a secret once — store it; it cannot be retrieved later. See Signature Verification.Response
boolean
Whether the webhook was created successfully
object
Show Properties
Show Properties
string
Unique identifier for the webhook
string
The account this webhook is attached to
string
"hosted" or "custom" depending on whether url was providedstring
The webhook URL (custom mode only)
string
SSE stream endpoint (hosted mode only)
string
Stored-events polling endpoint (both modes) — the catch-up path
string
Delivery log endpoint (custom mode only)
boolean
Whether failed POSTs are retried (custom mode only)
array
Event types this webhook listens for
boolean
Whether the webhook is active
string
HMAC secret. Returned only when
enable_signature: true and only at creation. Save it — there is no way to read it back later. Use /rotate-secret to generate a new one.{
"success": true,
"data": {
"webhook_id": "6789abcdef0123456789abcd",
"account_id": "69c127c37cae0494dd827286",
"mode": "hosted",
"stream_url": "/v2/webhooks/6789abcdef0123456789abcd/stream",
"events_url": "/v2/webhooks/6789abcdef0123456789abcd/events",
"events": ["accepted_invitation", "message_received"],
"is_active": true
},
"metadata": {
"action": "create_webhook",
"credits_consumed": 0,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
{
"success": true,
"data": {
"webhook_id": "6789abcdef0123456789abcd",
"account_id": "69c127c37cae0494dd827286",
"mode": "custom",
"url": "https://your-server.com/webhook",
"events": ["all"],
"is_active": true
},
"metadata": {
"action": "create_webhook",
"credits_consumed": 0,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
{
"success": false,
"error": {
"code": "INVALID_ACCOUNT",
"message": "Account not found"
},
"metadata": {
"action": "create_webhook",
"credits_consumed": 0,
"timestamp": "2025-01-15T10:30:00.000000"
}
}
Notes
- The webhook is created in an active and monitoring state — events start flowing immediately, no extra call required.
- You can create multiple webhooks for the same account with different event filters. Billing is per-account (not per-webhook), so adding a second webhook to the same account doesn’t double the credit cost.
- Stopping the last active webhook on an account (via
/stoporDELETE) halts monitoring and stops the credit billing automatically. Other webhooks on the same account are unaffected. - For custom mode, the webhook URL must return a
200status code to acknowledge receipt. - When
accepted_invitationis in the events list, the API performs an initial sync of your connections to enable detection of new acceptances. Likewise,invitation_receivedstores a baseline of your pending invitations — the existing backlog is never notified, only invitations received after creation.
Was this page helpful?