ThreadPage
All docs

Endpoint URLs

An endpoint URL turns each JSON event a service sends into a note in one thread: form answers, alerts, build results, GitHub pull requests or Slack messages. The service needs no token and no code from you, only the URL.

View as Markdown

On this page
  1. What an endpoint URL is
  2. Presets
  3. Send an event
  4. Template syntax
  5. Signing
  6. Duplicates
  7. Answers
  8. Limits
  9. Set up GitHub
  10. Set up Slack
  11. From n8n, Make or Zapier
  12. If it fails
  13. Next

What an endpoint URL is

  • The URL is the credential. Anyone who has it can add notes to its thread, so keep it like a password.
  • It adds notes to the one thread the owner chose. An event can’t choose another thread.
  • The owner creates it in ThreadPage, as a connection of its own on Add only. The API can’t create one.
  • It needs Pro with Sync turned on. Without them, events answer 409 and are not added.
  • The URL, and its secret if it has one, are shown once. Replace URL… gives new ones, and the old ones stop working at once.
  • It needs no permission for a shared thread: the owner chose that thread.
  • A connection has up to 10 endpoint URLs.

To create one: in Settings → Connections, go to Automations and choose Connect an automation → Add notes from another app. Choose the thread and what the service sends, then copy the URL and any secret into the service.

Presets

Presets
Preset in ThreadPagepresetThe service sendsEach event becomes
Text or Markdowntext{"text": "…"} or {"markdown": "…"}, with an optional ida note, as written (text) or formatted (markdown)
GitHub pull requestsgithubGitHub pull_request eventsa note when a pull request is opened, reopened, ready for review, merged or closed, with its title, link, repository, author and time; other events are ignored
GitHub releasesgithub_releaseGitHub release eventsa note when a release is published, with its name (or tag), link and release notes; other release actions are ignored
Slack messagesslackSlack Events APIa note for each new message, as Slack shows it, with its links and formatting; edits, deletions and joins are ignored; Slack’s URL check is answered
Custom templatecustomany JSON objectyour Markdown template, filled with fields of the event

Send an event

With Text or Markdown, send text (kept as written) or markdown (formatted). An id makes a retried event count once:

Shell
curl -X POST "$THREADPAGE_ENDPOINT_URL" \
  -H "Content-Type: application/json" \
  -d '{"text": "Build 412 passed."}'
Shell
curl -X POST "$THREADPAGE_ENDPOINT_URL" \
  -H "Content-Type: application/json" \
  -d '{"id": "build-412", "markdown": "**Build 412** passed. [Logs](https://ci.example.com/412)"}'
JSON
{"outcome": "accepted", "note_id": "4c2f8e19-7a6b-4d3e-b1c0-95e8f2a7d364", "diagnostic_id": "5b3e9a0c7d1f4e2a8b6c0d9e1f2a3b4c"}

Send an event to an endpoint URL

POST /in/{capability}

The owner adds an endpoint URL to a custom connection whose level adds notes (Add only, Read and add or Edit and organize) in Settings → Connections; each event becomes a note in the one thread they chose. The URL is the credential: no token is sent, and it is shown once. Signed endpoints check the signature over the raw body first (Standard Webhooks, GitHub X-Hub-Signature-256 or Slack v0, within five minutes). The preset decides which events become notes and how a repeated event is recognised for 30 days: the webhook-id header where nothing is signed or a Standard Webhooks signature covers it; otherwise the event's id in the body (id; event_id for Slack, whose signature covers the body only, so its webhook-id header is ignored), and for GitHub the SHA-256 of the signed body. Every answer except Slack's URL check has outcome and diagnostic_id, which is also the X-Request-ID header; an added note has its note_id.

Requirements
RequirementValue
CredentialThe endpoint URL itself; no token.
Parameters
NameInTypeDescription
capabilitypathstring, requiredThe secret part of the endpoint URL.

Answer 200: Slack's URL check, answered with its challenge.

Answer fields
FieldTypeDescription
challengestring, requiredSlack's challenge, sent back.
curl
curl -m 30 -X POST "$ENDPOINT_URL" \
  -H "Content-Type: application/json" \
  -d '{"id":"build-412","markdown":"**Build 412** passed on `main`."}'

Errors: reconnect_required 403, 409, write_not_allowed 403, 409, not_found 404, connection_unavailable 409, invalid_json 400, content_too_large 413, unsupported_media_type 415, thread_archived 409, invalid_signature 401, secret_unavailable 401, method_not_allowed 405, endpoint_paused 503, missing_field 422, empty_note 422.

Template syntax

A template is Markdown with fields from the event, written as JSON Pointers in double braces.

Template syntax
WriteInserts
{{/name}}the field; the event must have it
{{/company?}}the field, or nothing when it is missing
{{/profile|url}}the field as a link
{{/created_at|timestamp}}the field as a readable UTC time
{{/summary|markdown}}the field with its own Markdown formatting

Other values are inserted as plain text, and null inserts nothing. The dialog in ThreadPage previews the note from a sample event.

Markdown
New lead: **{{/name}}**
{{/company?}}
{{/profile|url}}
Received {{/created_at|timestamp}}

An event that lacks a required field answers 422.

Signing

An endpoint can require a signature over the raw body, checked before anything is parsed. A timestamp must be within 5 minutes.

Signing
PresetSignature
Text or Markdown, Custom templatenone, or Standard Webhooks: webhook-id, webhook-timestamp and webhook-signature, with the whsec_ secret ThreadPage made. Each event needs its own webhook-id.
GitHub pull requestsGitHub’s X-Hub-Signature-256, with the secret ThreadPage made for it.
Slack messagesSlack’s v0 signature, with your Slack app’s signing secret.

A Standard Webhooks request, signed the way ThreadPage signs its own webhooks:

import crypto from 'node:crypto';

const secret = process.env.THREADPAGE_ENDPOINT_SECRET; // whsec_…
const body = JSON.stringify({ id: 'build-412', markdown: '**Build 412** passed.' });
const id = `msg_${crypto.randomUUID()}`; // one per event, the same when you retry it
const timestamp = String(Math.floor(Date.now() / 1000));
const key = Buffer.from(secret.slice('whsec_'.length), 'base64');
const signature = crypto.createHmac('sha256', key).update(`${id}.${timestamp}.${body}`).digest('base64');

const answer = await fetch(process.env.THREADPAGE_ENDPOINT_URL, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'webhook-id': id,
    'webhook-timestamp': timestamp,
    'webhook-signature': `v1,${signature}`,
  },
  body,
});
console.log(answer.status, await answer.json());

Duplicates

ThreadPage remembers an event for 30 days and adds it once:

  • by its webhook-id, when the endpoint is unsigned or a Standard Webhooks signature covers it;
  • otherwise by the event’s own ID: id in the body, or event_id for Slack;
  • for GitHub, by the SHA-256 of the signed body.

A repeat answers 202 with outcome: "duplicate".

Answers

Every answer except Slack’s URL check is JSON with outcome and diagnostic_id, which is also the X-Request-ID header. An added note also has its note_id; a refusal has code and message.

Answers
outcomeMeans
acceptedThe note was added.
duplicateThe same event was added before.
ignoredNot an event this endpoint adds, such as a GitHub ping or an edited Slack message.
rejectedThe request itself was refused: no such endpoint, a paused one, a bad signature, or a body that is not JSON.
blockedThe event is fine, but the note can’t be added now: a limit, or a thread or connection that can’t take notes.
failedThe event can’t become a note: a field the template needs is missing, or the note would be empty or too large.
Answers
StatusMeans
200Slack’s URL check, answered with its challenge.
202accepted, duplicate or ignored.
400The body is not one JSON object, repeats a key or nests too deeply.
401The signature or its webhook-id is missing, or the signature is wrong or older than 5 minutes.
404No such endpoint: the URL was replaced or deleted, or the connection was disconnected.
405Not sent with POST.
409Not added: the thread is in Trash, archived or outside the connection’s access, or the connection can’t add notes now. token_expired means the connection’s token ended: the owner chooses Renew. reconnect_required means the token was retired: the owner chooses Replace token….
413The body is over 256 KiB, the note would be over 256 KiB, or its Markdown over 32 KiB.
415Not sent as uncompressed JSON.
422The event has no text or markdown, lacks a field the template needs, or makes an empty note.
429A limit was reached: rate_limited with details.reason and details.scope. Wait Retry-After seconds.
503The endpoint is paused, the request took too long (timed_out) or ThreadPage is busy. Wait Retry-After seconds.

Each endpoint’s Events list in ThreadPage shows its last 20 outcomes, and its row says when the latest event failed. ThreadPage keeps no copy of an event, only its outcome. Events refused while the endpoint is paused or over a limit are not kept to add later, and many services don’t send them again.

Limits

Limits
LimitValue
An event’s body256 KiB
A markdown field, or a template’s note32 KiB
Plain textthe 256 KiB of a note
Events, all of a connection’s endpoint URLs together60 a minute (events_minute)
Imagesnot taken; HTML stays text, and links work only for http and https

Each note also counts toward the connection’s write limits.

Set up GitHub

  1. Create the endpoint with GitHub pull requests and copy its URL and secret.
  2. In the repository’s Settings → Webhooks, paste the URL as Payload URL, choose Content type application/json, paste the secret, and pick Pull requests.

GitHub does not resend failed deliveries by itself; use Redeliver in the webhook’s Recent Deliveries.

Set up Slack

  1. Create the endpoint with Slack messages, and give ThreadPage your Slack app’s signing secret.
  2. In your Slack app’s Event Subscriptions, paste the URL as the Request URL. ThreadPage answers Slack’s URL check.

From n8n, Make or Zapier

Use an HTTP or webhook step: method POST, body type JSON, the text or markdown field, and an id built from the item, so a retried run adds the note once.

If it fails

Errors
CodeHTTPWhenWhat to doRetryable
invalid_signature401The signature or its webhook-id is missing, wrong or older than 5 minutes.Sign the raw body with the endpoint's secret, with a fresh timestamp.No
secret_unavailable401The endpoint can no longer check signatures.Ask the owner to add the endpoint again.No
method_not_allowed405Not sent with POST.Send POST.No
endpoint_paused503The owner paused the endpoint; the event is not kept.Ask the owner to resume it, then send the event again.Yes
missing_field422The event lacks a field the template needs.Send the field, or mark it optional with ? in the template.No
empty_note422The event makes an empty note.Send text or markdown.No

Next