Get LinkedIn inbox messages
Retrieve LinkedIn replies received from prospects through your Woodpecker campaigns, including responses to direct messages, InMails, and connection requests. Each reply includes the message content, sender details, LinkedIn conversation URL, and identifiers that can be used to continue the conversation through the API for direct messages and InMails.
Replies are returned from newest to oldest, with pagination support. You can filter results by LinkedIn account or campaign to retrieve only the conversations relevant to your workflow.
Reply detection for InMail responses is supported for LinkedIn Premium accounts but not for Sales Navigator accounts.
Request
Endpoint
GET https://api.woodpecker.co/rest/v2/linkedin/inbox/messages
Headers
x-api-key: {YOUR_API_KEY}
For details on how to authenticate your requests, please see the authentication guide.
Parameters
All query parameters are optional. By default, the endpoint returns the 10 latest LinkedIn replies available in your Woodpecker account.
| Parameter | Required | Type | Description |
|---|---|---|---|
account_ids | No | string (comma-separated integers) | Returns replies received by the specified LinkedIn accounts. Use the LinkedIn accounts endpoint to find account IDs |
campaign_ids | No | string (comma-separated integers) | Returns replies associated with the specified campaigns |
per_page | No | integer | Number of replies per page. Default: 10, minimum: 1, maximum: 50 |
next_page_cursor | No | string | Cursor returned in pagination.next_page_cursor for retrieving the next page. Mutually exclusive with previous_page_cursor. See Pagination |
previous_page_cursor | No | string | Cursor returned in pagination.previous_page_cursor for retrieving the previous page. Mutually exclusive with next_page_cursor. See Pagination |
Request samples
Retrieve the latest replies for a LinkedIn account
- cURL
- Python
- Java
- Node.js
- PHP
curl --request GET \
--url "https://api.woodpecker.co/rest/v2/linkedin/inbox/messages?account_ids=567567&per_page=10" \
--header "x-api-key: {YOUR_API_KEY}"
import requests
def list_linkedin_inbox_messages():
url = "https://api.woodpecker.co/rest/v2/linkedin/inbox/messages"
headers = {
"x-api-key": "{YOUR_API_KEY}"
}
params = {
"account_ids": "567567",
"per_page": 10
}
response = requests.get(url, headers=headers, params=params)
if response.status_code == 200:
return response.json()
raise Exception(f"GET request failed: {response.status_code}, {response.text}")
if __name__ == "__main__":
try:
print("GET response:", list_linkedin_inbox_messages())
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 {
String url = "https://api.woodpecker.co/rest/v2/linkedin/inbox/messages?account_ids=567567&per_page=10";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("x-api-key", "{YOUR_API_KEY}")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
System.out.println("GET response: " + response.body());
} else {
throw new Exception(
"GET request failed: " + response.statusCode() + ", " + response.body()
);
}
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}
const axios = require('axios');
async function listLinkedinInboxMessages() {
const url = 'https://api.woodpecker.co/rest/v2/linkedin/inbox/messages';
const headers = {
'x-api-key': '{YOUR_API_KEY}'
};
const params = {
account_ids: '567567',
per_page: 10
};
try {
const response = await axios.get(url, { headers, params });
console.log('GET response:', response.data);
} catch (error) {
console.error(
'GET request failed:',
error.response ? error.response.status : error.message
);
}
}
listLinkedinInboxMessages();
<?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'),
],
]);
try {
$response = $client->get('linkedin/inbox/messages', [
'query' => [
'account_ids' => '567567',
'per_page' => 10,
],
]);
echo $response->getStatusCode(), "\n";
echo $response->getBody(), "\n";
} catch (RequestException $e) {
echo "Error: ", $e->getMessage(), "\n";
if ($e->hasResponse()) {
echo $e->getResponse()->getBody(), "\n";
}
}
Response
Response examples
- 200
- 400
- 401
- 500
A page of LinkedIn replies, ordered from newest to oldest. content is an empty array when no replies match the request.
{
"content": [
{
"id": 123123,
"prospect_id": 234234,
"prospect_linkedin_profile": "https://www.linkedin.com/in/jane-doe-example",
"campaign_id": 345345,
"sent_id": 456456,
"orig_sent_action_type": "DIRECT_MESSAGE",
"account_id": 567567,
"stamp": "2026-08-18T13:08:09+02:00",
"from_name": "Jane Doe",
"from_profile_id": "jane-doe-profile-id",
"recipient": "https://www.linkedin.com/in/alex-smith-example",
"linkedin_thread_url": "https://www.linkedin.com/messaging/thread/example-thread-123123",
"body": "Thanks for reaching out. I'd be happy to learn more.",
"attachments": [
{
"type": "file",
"name": "project-overview.pdf",
"media_type": "application/pdf",
"byte_size": 245760,
"download_url": "https://www.linkedin.com/dms/attachments/example-project-overview.pdf"
}
]
},
{
"id": 123122,
"prospect_id": 234235,
"prospect_linkedin_profile": "https://www.linkedin.com/in/john-smith-example",
"campaign_id": 345345,
"sent_id": 456457,
"orig_sent_action_type": "INMAIL_MESSAGE",
"account_id": 567567,
"stamp": "2026-08-18T12:35:00+02:00",
"from_name": "John Smith",
"from_profile_id": "john-smith-profile-id",
"recipient": "https://www.linkedin.com/in/alex-smith-example",
"linkedin_thread_url": "https://www.linkedin.com/messaging/thread/example-thread-123122",
"body": "Could you send me a short overview?",
"attachments": []
}
],
"pagination": {
"previous_page_cursor": null,
"next_page_cursor": "T2ggSGkgTWFyayE="
}
}
Body schema
| Field | Type | Description |
|---|---|---|
content | array[object] | LinkedIn replies on the requested page |
└─ [].id | integer | Unique ID of the LinkedIn reply |
└─ [].prospect_id | integer/null | ID of the prospect associated with the reply |
└─ [].prospect_linkedin_profile | string/null | LinkedIn profile URL stored for the prospect |
└─ [].campaign_id | integer/null | ID of the campaign in which the reply was received |
└─ [].sent_id | integer/null | ID of the original LinkedIn action associated with the reply |
└─ [].orig_sent_action_type | string/null | Type of the original LinkedIn action: CONNECTION_REQUEST, DIRECT_MESSAGE, or INMAIL_MESSAGE |
└─ [].account_id | integer/null | ID of the LinkedIn account that received the reply |
└─ [].stamp | string/null | Time the reply was received in ISO 8601 format, including an offset |
└─ [].from_name | string/null | Name of the person who sent the reply |
└─ [].from_profile_id | string/null | LinkedIn profile ID of the person who sent the reply |
└─ [].recipient | string/null | LinkedIn profile URL of the account that received the reply |
└─ [].linkedin_thread_url | string/null | URL of the LinkedIn conversation containing the reply |
└─ [].body | string/null | Content of the reply |
└─ [].attachments | array[object] | Attachments associated with the reply; empty when none are available. The schema lists the common attachment fields. Depending on the attachment type, an object may include additional fields. Files are hosted externally, to access a download_url, open it in a browser logged into the LinkedIn account participating in the conversation |
└─ [].type | string | Attachment type: audio, video, file, vectorImage, externalMedia, or repliedMessageContent (quoted message content) |
└─ [].download_url | string | External download URL, when present. Not available for every attachment; repliedMessageContent may have no file URL |
pagination | object | Cursors for moving through the result set |
└─ previous_page_cursor | string/null | Cursor for the previous page. null means the current page is the first page |
└─ next_page_cursor | string/null | Cursor for the next page. null means the current page is the last page |
Invalid query parameters return invalid_fields, with details in fields. For example, per_page=51 returns:
{
"type": "validation_error",
"code": "invalid_fields",
"message": "Invalid field(s).",
"request_id": "12345678-1234-1234-1234-123456789012",
"fields": [
{
"field": "per_page",
"issue": "Must be between 1 and 50 (inclusive)"
}
]
}
A query can also be rejected with bad_request, without field-level details:
{
"type": "validation_error",
"code": "bad_request",
"message": "Invalid query parameters",
"request_id": "12345678-1234-1234-1234-123456789012"
}
Body schema
| Field | Type | Description |
|---|---|---|
type | string | Error type: validation_error |
code | string | Error code: invalid_fields or bad_request |
message | string | Human-readable error summary |
request_id | string/null | Request identifier, or null when unavailable |
fields | array[object] | Invalid query parameters; present for invalid_fields and omitted for bad_request |
└─ [].field | string | Name of the invalid query parameter |
└─ [].issue | string | Description of the validation issue |
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 |
An unexpected server error occurred. Try the request again later.
{
"type": "internal_error",
"code": "internal_server_error",
"message": "An unexpected error occurred on the server.",
"request_id": "12345678-1234-1234-1234-123456789012"
}
Body schema
| Field | Type | Description |
|---|---|---|
type | string | Error type: internal_error |
code | string | Error code: internal_server_error |
message | string | Human-readable error summary |
request_id | string/null | Request identifier, or null when unavailable |
Pagination
This endpoint uses cursor-based pagination. Replies remain ordered from newest to oldest on every page. Keep the same filters when moving between pages.
The cursor values shown on this page are illustrative placeholders, not valid API cursors. For actual requests, use the cursor returned by the API.
- To start from the first page, omit both cursor parameters
- To move forward, send the value returned in
pagination.next_page_cursorasnext_page_cursor - To move backward, send the value returned in
pagination.previous_page_cursorasprevious_page_cursor - Send only one cursor per request. A
nullcursor means there is no page in that direction
Request the first page
curl --request GET \
--url "https://api.woodpecker.co/rest/v2/linkedin/inbox/messages?account_ids=567567&per_page=10" \
--header "x-api-key: {YOUR_API_KEY}"
On the first page, previous_page_cursor is null. A non-null next_page_cursor means more replies are available:
{
"previous_page_cursor": null,
"next_page_cursor": "T2ggSGkgTWFyayE="
}
Request the next page
Pass the returned next_page_cursor with the same filters:
curl --request GET \
--url "https://api.woodpecker.co/rest/v2/linkedin/inbox/messages?account_ids=567567&per_page=10&next_page_cursor=T2ggSGkgTWFyayE=" \
--header "x-api-key: {YOUR_API_KEY}"
A middle page can include cursors in both directions:
{
"previous_page_cursor": "RG9uJ3QgdGVsbCBhbnlvbmUh",
"next_page_cursor": "SXQncyBhIHRyYXAh"
}
Request the previous page
Pass previous_page_cursor from the current response:
curl --request GET \
--url "https://api.woodpecker.co/rest/v2/linkedin/inbox/messages?account_ids=567567&per_page=10&previous_page_cursor=RG9uJ3QgdGVsbCBhbnlvbmUh" \
--header "x-api-key: {YOUR_API_KEY}"