Do it yourself
#-
Open Settings and choose Developers under the workspace.
-
Under Webhooks, choose Add endpoint.
-
In URL, enter a public
httpsaddress on your server. -
In Which events, enter exact event types or a family with a star, like
domain.* mail.*. Leave it empty to get every event. -
Choose Add endpoint. If Playcode asks you to confirm it's you, enter the code from the email it sends.
-
Copy the signing secret: "Copy it now - you won't see this token again." It starts with
pcwh_. Keep it on your server, for example asPLAYCODE_WEBHOOK_SECRET. -
On your server, verify each delivery and answer with a 2xx status within 10 seconds:
import { verifyWebhook } from '@playcode/sdk' const { id, event } = verifyWebhook({ signature: req.headers['playcode-signature'], body: rawBody, // the body exactly as it arrived secret: process.env.PLAYCODE_WEBHOOK_SECRET!, }) -
Check the deliveries: choose Deliveries on the endpoint's row. Each one shows its event, its status and your server's answer. Send again runs a failed delivery now.
What a delivery looks like
#| Header | Value |
|---|---|
Playcode-Signature |
t=<unix seconds>,v1=<hex> |
Playcode-Delivery |
The delivery's id, the same as id in the body |
Playcode-Event |
The event type, like domain.updated |
Content-Type |
application/json |
{
"id": "<delivery id>",
"event": {
"uid": "<event id>",
"type": "domain.updated",
"subjectType": "domain",
"subjectUid": "<the domain's id>",
"workspaceUid": "<workspace id>",
"via": "api",
"data": { "args": { "uid": "<the domain's id>", "input": { "autoRenew": true } } },
"previous": { "autoRenew": false },
"createdAt": "2026-10-10T09:00:00.000Z"
}
}
The event types and their fields: see Activity.
Verify without the SDK
#Compute HMAC-SHA256 with the secret over the text t, a dot, and the raw body; compare its hex with v1 in constant time. Refuse a delivery whose t is more than 300 seconds from now: the SDK does the same.
If it doesn't work
#"A webhook URL is https."
#Use an https address. Plain http is refused.
"... resolves to ..., which is not a public address." or "... does not resolve."
#The address must resolve to a public IP address. Playcode checks it again before every delivery.
"A webhook URL carries no credentials."
#Remove the user name and password from the address. Check the signature instead.
"A workspace holds at most 20 webhook endpoints; delete one first."
#Remove an endpoint you no longer use.
A delivery shows failed
#Its row says why: "the endpoint answered" with your server's status, "no answer" when it did not answer within 10 seconds, or "redirects are never followed". Fix the server, then choose Send again.
verifyWebhook throws "the signature does not match this body and secret"
#Pass the raw body, before any JSON parsing, and the secret of this endpoint.
verifyWebhook throws "the delivery is ... s old, past the 300 s tolerance"
#Your server's clock is off, or you checked an old delivery. Every attempt is signed again with a fresh time.
You lost the signing secret
#The Developers page has no rotate button yet. Ask the agent in any project chat in the workspace to rotate the secret, or remove the endpoint and add it again: the new endpoint gets a new secret.
Limits
#- Up to 20 endpoints per workspace. Any member of the workspace can add one, and an endpoint gets every matching event of the whole workspace.
- Playcode tries a delivery up to 8 times: at once, then after 30 seconds, 2 minutes, 10 minutes, 30 minutes, 2 hours, 8 hours and 24 hours.
- An endpoint that fails 20 times in a row, with no success for 3 days, gets no new deliveries until one succeeds. Send again tries one.
- Deliveries can arrive out of order, and the same delivery can arrive twice: deduplicate on
Playcode-Delivery. - Redirects are never followed. Each attempt waits 10 seconds for an answer.
- The delivery log keeps 30 days.
- A publish's result is not an event:
projectPublish.createdmarks the start. PollprojectPublishGetfor the end. - Access tokens cannot manage webhooks. A signed-in person manages them in the app: on the Developers page, or by asking the agent in a chat.