Receive webhooks
Nội dung này hiện chưa có sẵn bằng ngôn ngữ của bạn.
A webhook is a URL of yours that Pickpost calls when something happens in a workspace. Use it to post in a chat channel when a post goes out, to open a ticket when one fails, or to keep another system in sync.
Add one
Section titled “Add one”In the app, open API & MCP and find Webhooks. Paste an https URL, pick the events, and click Add webhook. Copy the signing secret that appears (whsec_…): it is shown once.
Only owners and admins of a workspace can add, change or see webhooks. They are not available to API tokens, MCP clients or the assistant, because a webhook receives the workspace’s posts wherever its URL points.
The URL must be https, reachable on the public internet, and have no user name or password in it. A workspace has at most 10 webhooks; its plan may allow fewer (every plan allows 10 today). After a move to a plan with fewer, the oldest webhooks within the new limit keep working, and deliveries to the others fail with the reason until the workspace upgrades or deletes some.
Events
Section titled “Events”| Event | When | data |
|---|---|---|
post.published |
A post went out to every account it was for | post: the post, as posts.get returns it |
post.failed |
Publishing a post ended, and at least one account failed or could not be confirmed | post: the post, with results per account |
account.disconnected |
An account stopped working and needs to be reconnected | account: the account (no tokens) |
ping |
You clicked Send test | message |
Each event goes to every webhook that asked for it; a ping goes only to the webhook you tested. A failed post that is published again later sends a new event.
What arrives
Section titled “What arrives”A POST with a JSON body. This test event was captured as it arrived:
POST /pickpost-webhook HTTP/1.1Content-Type: application/jsonUser-Agent: Pickpost-Webhooks/1Pickpost-Event: pingPickpost-Delivery: whd_d5c6c46ff45c4c57Pickpost-Signature: t=1790471647,v1=04237f94dd56a46b9b069830c005f41c73a66363418364ffaad74da3ff36259eContent-Length: 166
{"id":"evt_b0f6e369f33c4167","type":"ping","createdAt":"2026-09-27T01:14:02.435698Z","workspaceId":"20b9511c8bcb4203","data":{"message":"Webhook test from Pickpost"}}Every event has the same envelope: id (the event), type, createdAt, workspaceId and data. A post.published body looks like this:
{ "id": "evt_36a0f162588e4bff", "type": "post.published", "createdAt": "2026-09-27T00:48:51.682264Z", "workspaceId": "20b9511c8bcb4203", "data": { "post": { "id": "36a2e7fa9147424d", "status": "Published", "accountIds": ["fd3c012b05d54320"], "versions": [{ "content": [{ "body": "Our autumn menu is live.", "media": [] }], "options": {} }], "scheduledAt": "2026-09-27T00:48:51.662958Z", "results": [{ "accountId": "fd3c012b05d54320", "ok": true, "externalId": "dryrun-1790470131-d3ba1352", "uncertain": false }], "updatedAt": "2026-09-27T00:48:51.681385Z" } }}The example comes from a dry-run server, so the externalId is made up. On a real server it is the network’s post id.
Answer with any 2xx status within 10 seconds. Pickpost reads only the status, not what you send back. Do slow work after answering.
Check the signature
Section titled “Check the signature”Pickpost-Signature is t=<unix seconds>,v1=<hex>, where the hex is the HMAC-SHA256 of <t>.<body> with your signing secret. Compute it over the raw body, exactly as received, before parsing the JSON. Refuse a delivery whose signature does not match, or whose t is more than 5 minutes from your clock.
import hmac, hashlib, time
def verify(secret: str, header: str, body: bytes, tolerance: int = 300, now=None) -> bool: """body: the raw request body, exactly as received (before any JSON parsing).""" parts = dict(p.split("=", 1) for p in header.split(",")) t, sent = parts.get("t", ""), parts.get("v1", "") if not t.isdigit() or abs((now or time.time()) - int(t)) > tolerance: return False expected = hmac.new(secret.encode(), t.encode() + b"." + body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, sent)import { createHmac, timingSafeEqual } from "node:crypto";
// body: the raw request body (a Buffer), exactly as received, before JSON.parse.export function verify(secret, header, body, toleranceSeconds = 300, now = Date.now() / 1000) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2))); const t = Number(parts.t); if (!Number.isInteger(t) || Math.abs(now - t) > toleranceSeconds) return false; const expected = createHmac("sha256", secret).update(`${t}.`).update(body).digest(); const sent = Buffer.from(parts.v1 ?? "", "hex"); return sent.length === expected.length && timingSafeEqual(sent, expected);}Both were checked against a real delivery: the delivery passes, and a changed body or a delivery older than 5 minutes fails.
Retries and duplicates
Section titled “Retries and duplicates”A delivery that gets no 2xx is tried again after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours, then it is given up. Timeouts, refused connections and redirects count as failures. Pickpost does not follow redirects, so use the final URL.
Deliveries are sent at least once, so the same event can arrive twice, for example when Pickpost restarts during a delivery. Keep the id of the events you handled and skip repeats. Pickpost-Delivery names one delivery to one webhook and stays the same across its retries.
After 10 deliveries in a row are given up, the webhook is paused and receives nothing. Events that happen while it is paused are not kept for later. Fix the endpoint, then click Resume.
The delivery log
Section titled “The delivery log”Deliveries under each webhook shows the last 50: the event, the state (pending, sending, delivered, failed), the attempts, the last answer or error, and when the next try is due. Send again queues a delivery once more, from the first attempt. The log keeps 30 days.