Reply to a LinkedIn inbox message
Continue a LinkedIn conversation after a prospect replies to a direct message or InMail sent through your Woodpecker campaign. First, retrieve the received reply, then use its ID to send your response.
Your response is sent from the LinkedIn account that received the reply. Responses to direct messages are sent as direct messages; responses to InMails are sent as InMails.
Request
Endpoint
POST https://api.woodpecker.co/rest/v2/linkedin/inbox/messages/{id}/reply
Headers
x-api-key: {YOUR_API_KEY}
Content-Type: application/json
For details on how to authenticate your requests, please see the authentication guide.
Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
id | Yes | integer | Path parameter. ID of the received reply: content[].id from the LinkedIn inbox messages endpoint or [].replied.id from the LinkedIn reply webhook |
Body
{
"message": "Thanks for your reply. Here is a short overview of our project."
}
The request accepts the message text only. You cannot send attachments or select a different sending account or message type.
Body schema
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Message text; cannot be empty or contain only whitespace. Maximum: 6,000 characters for direct messages, or 1,900 for InMails |
Sending uses the account's action limits for the corresponding message type, shared between campaigns and API requests. Direct messages count toward a rolling 24-hour limit, while InMails count toward a rolling 31-day limit. The exact limits depend on the LinkedIn account's configuration. If a limit is reached, the API returns 429.
Request samples
Reply to a received direct message
The examples use reply ID 123123 from the inbox response example.
- cURL
- Python
- Java
- Node.js
- PHP
curl --request POST \
--url "https://api.woodpecker.co/rest/v2/linkedin/inbox/messages/123123/reply" \
--header "x-api-key: {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--data '{
"message": "Thanks for your reply. Here is a short overview of our project."
}'
import requests
def reply_to_linkedin_message(reply_id):
url = f"https://api.woodpecker.co/rest/v2/linkedin/inbox/messages/{reply_id}/reply"
headers = {
"x-api-key": "{YOUR_API_KEY}",
"Content-Type": "application/json"
}
payload = {
"message": "Thanks for your reply. Here is a short overview of our project."
}
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 204:
return "LinkedIn reply sent."
raise Exception(f"POST request failed: {response.status_code}, {response.text}")
if __name__ == "__main__":
try:
print(reply_to_linkedin_message(123123))
except Exception as error:
print("Error:", error)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class WoodpeckerApiClient {
public static void main(String[] args) {
try {
long replyId = 123123;
String url = "https://api.woodpecker.co/rest/v2/linkedin/inbox/messages/" + replyId + "/reply";
String jsonData = """
{
"message": "Thanks for your reply. Here is a short overview of our project."
}
""";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("x-api-key", "{YOUR_API_KEY}")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonData))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 204) {
System.out.println("LinkedIn reply sent.");
} else {
throw new Exception(
"POST request failed: " + response.statusCode() + ", " + response.body()
);
}
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}
const axios = require('axios');
async function replyToLinkedinMessage(replyId) {
const url = `https://api.woodpecker.co/rest/v2/linkedin/inbox/messages/${replyId}/reply`;
const headers = {
'x-api-key': '{YOUR_API_KEY}',
'Content-Type': 'application/json'
};
const data = {
message: 'Thanks for your reply. Here is a short overview of our project.'
};
try {
await axios.post(url, data, { headers });
console.log('LinkedIn reply sent.');
} catch (error) {
console.error(
'POST request failed:',
error.response ? error.response.status : error.message
);
}
}
replyToLinkedinMessage(123123);
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$client = new Client([
'base_uri' => 'https://api.woodpecker.co/rest/v2/',
'headers' => [
'x-api-key' => getenv('WOODPECKER_API_KEY'),
'Content-Type' => 'application/json',
],
]);
try {
$replyId = 123123;
$response = $client->post("linkedin/inbox/messages/{$replyId}/reply", [
'json' => [
'message' => 'Thanks for your reply. Here is a short overview of our project.',
],
]);
echo $response->getStatusCode(), "\n";
} catch (RequestException $e) {
echo "Error: ", $e->getMessage(), "\n";
if ($e->hasResponse()) {
echo $e->getResponse()->getBody(), "\n";
}
}
Response
Response examples
- 204
- 400
- 401
- 404
- 422
- 429
- 500
The LinkedIn reply was sent successfully.
Status: 204
Body: none
The path parameter id is not a valid integer.
{
"type": "validation_error",
"code": "invalid_request",
"message": "Invalid request.",
"request_id": "12345678-1234-1234-1234-123456789012"
}
Body schema
| Field | Type | Description |
|---|---|---|
type | string | Error type: validation_error |
code | string | Error code: invalid_request |
message | string | Human-readable error summary |
request_id | string/null | Request identifier, or null when unavailable |
The API key is missing or invalid. Review the authentication guide.
{
"title": "Unauthorized",
"status": 401,
"detail": "Invalid api key",
"timestamp": "2026-08-18 11:08:09"
}
Body schema
| Field | Type | Description |
|---|---|---|
title | string | A short title describing the error |
status | integer | HTTP status code |
detail | string | Details about the authorization error |
timestamp | string | Time the error occurred in YYYY-MM-DD HH:MM:SS UTC format |
The reply with given id is unavailable in your Woodpecker account.
{
"type": "validation_error",
"code": "reply_not_found",
"message": "Reply not found.",
"request_id": "12345678-1234-1234-1234-123456789012"
}
Body schema
| Field | Type | Description |
|---|---|---|
type | string | Error type: validation_error |
code | string | Error code: reply_not_found |
message | string | Human-readable error summary |
request_id | string/null | Request identifier, or null when unavailable |
The message is invalid, the LinkedIn account is not connected, or the message could not be sent.
The code identifies the error: invalid_fields, account_not_connected, or send_failed. For invalid_fields, details are provided in fields, as in this example:
{
"type": "validation_error",
"code": "invalid_fields",
"message": "Invalid field(s)",
"request_id": "12345678-1234-1234-1234-123456789012",
"fields": [
{
"field": "message",
"issue": "too_long",
"value": "Max allowed 6000"
}
]
}
Body schema
| Field | Type | Description |
|---|---|---|
type | string | Error type: validation_error |
code | string | Error code: invalid_fields, account_not_connected, or send_failed |
message | string | Human-readable error summary |
request_id | string/null | Request identifier, or null when unavailable |
fields | array[object] | Validation details; present for invalid_fields and omitted for the other error codes |
└─ [].field | string | Name of the invalid field: message |
└─ [].issue | string | Validation issue: required or too_long |
└─ [].value | string/null | null for required; the character limit for too_long, such as Max allowed 6000 |
The LinkedIn account's action quota for the message type has been reached, or throttled. Campaign sends and API replies share the same quota. Direct messages use a rolling 24-hour window; InMails use a rolling 31-day window.
{
"type": "validation_error",
"code": "action_limit_exceeded",
"message": "Daily action limit or action request rate limit exceeded.",
"request_id": "12345678-1234-1234-1234-123456789012"
}
Body schema
| Field | Type | Description |
|---|---|---|
type | string | Error type: validation_error |
code | string | Error code: action_limit_exceeded |
message | string | Human-readable error summary |
request_id | string/null | Request identifier, or null when unavailable |
An unexpected server error occurred.
{
"type": "internal_error",
"code": "internal_server_error",
"message": "An unexpected error occurred on the server.",
"request_id": "12345678-1234-1234-1234-123456789012"
}