Webhooks

A webhook is a signed POST that Playcode sends to your server when something happens in your workspace: a domain changed, a publish started, an email was delivered or bounced. You add the endpoint on the Developers page and verify each delivery with its signing secret.

On this page

Do it yourself

#
  1. Open Settings and choose Developers under the workspace.

  2. Under Webhooks, choose Add endpoint.

  3. In URL, enter a public https address on your server.

  4. In Which events, enter exact event types or a family with a star, like domain.* mail.*. Leave it empty to get every event.

  5. Choose Add endpoint. If Playcode asks you to confirm it's you, enter the code from the email it sends.

  6. 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 as PLAYCODE_WEBHOOK_SECRET.

  7. 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!,
    })
    
  8. 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.created marks the start. Poll projectPublishGet for 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.