Deploy Hooks
Rebuild and redeploy your live site from WordPress, Sanity, a CI step, a cron job or any service that can call a URL.
Deploy Hooks
A deploy hook is a private URL that rebuilds and redeploys your live site. Point your CMS at it, and publishing a post updates the site without anyone opening the editor.
What a deploy hook deploys
A deploy hook builds the site from your GitHub repository's default branch, the same code your live site already runs. While building, the site fetches its data again from WordPress, Sanity or your other sources.
| Goes live with a deploy hook | |
|---|---|
| New or updated content in WordPress, Sanity and other sources | Yes |
| Code already pushed to GitHub (from PhantomStudio, a teammate or CI) | Yes |
| Edits in the editor you haven't published | No: use Publish |
| Drafts on other branches | No |
The hook never commits or pushes anything. That's deliberate: the hook means "the content changed, rebuild the site", and Publish means "my changes are ready, ship them". A post published in WordPress can't take half-finished design work live with it.
Pages set to Dynamic read your CMS on every request, so they show new content without a deploy hook. You need a hook for static pages, and for Dynamic, cached pages when you don't want to wait for the cache to expire.
Before you start
- Deploy the site once to Vercel or Cloudflare Workers. A deploy hook redeploys an existing deployment; until there is one, calling it answers "No deployment configured for this repository".
- The project owner's GitHub account must be linked. Hook deploys run as the project owner, so they work even when nobody is signed in.
Create a deploy hook
- In the editor, open Publish ▾ › Deploy hook. (WordPress projects also have it under Site › WordPress › Deployment.)
- Click Create deploy hook.
- Copy the Deploy hook URL.
Each project has one deploy hook. You don't need to connect WordPress or any other source to create it.
Trigger a deploy
With the URL
Send a request to the deploy hook URL. GET and POST both work, and no headers or body are needed. Whatever body the sender includes is ignored, so a CMS can send its usual payload.
The examples on this page use https://phantomwp.com, which keeps answering for PhantomStudio. Copy your own URL from the deploy hook panel rather than typing it: it has your secret in it, and the panel shows the address to use.
curl -X POST 'https://phantomwp.com/api/webhook/deploy/<secret>'Opening the URL in a browser deploys too.
With the secret in a header
URLs end up in server logs, browser history and CMS delivery logs; headers usually don't. If the sender can set headers, send the secret in X-Webhook-Secret to the base URL instead. You'll find this form under Keep the secret out of the URL in the deploy hook panel.
curl -X POST 'https://phantomwp.com/api/webhook/deploy' \
-H 'X-Webhook-Secret: <secret>'Setups from before the deploy hook URL existed send {"repo": "<project name>"} as the JSON body with the header. They keep working.
The response
A successful call answers 200 and starts the build:
{
"success": true,
"message": "Deployment triggered successfully",
"deployment": { "status": "building", "provider": "vercel" }
}Every call starts a new deploy. Bulk-editing 20 posts in WordPress can start 20 builds; most hosts cancel or queue the older ones, but it uses build minutes.
Rebuild when WordPress content changes
Site › WordPress › Deployment shows a ready-made snippet with your URL filled in. Add it to your theme's functions.php or a small plugin:
add_action('publish_post', 'trigger_phantomwp_deploy');
add_action('publish_page', 'trigger_phantomwp_deploy');
function trigger_phantomwp_deploy($post_id) {
// Skip autosaves and revisions
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (wp_is_post_revision($post_id)) return;
wp_remote_post('https://phantomwp.com/api/webhook/deploy/<secret>');
}It deploys whenever a post or page is published or updated. For a custom post type, add a line with its name, for example add_action('publish_event', 'trigger_phantomwp_deploy');. To keep the secret out of URLs, send it as a header instead:
wp_remote_post('https://phantomwp.com/api/webhook/deploy', array(
'headers' => array('X-Webhook-Secret' => '<secret>'),
));Rebuild when Sanity content changes
The Sanity connection dialog links to the deploy hook. To set up the webhook in Sanity:
- Open sanity.io/manage, pick your project, then API › Webhooks › Create webhook.
- Paste the deploy hook URL as the URL.
- Pick the Dataset your site reads (usually
production). - Trigger on Create, Update and Delete.
- Optionally add a Filter so only content your site shows deploys, for example
_type in ["post", "page"]. - Leave drafts out, so only published changes deploy, and save.
Other services
Anything that can call a URL can deploy your site:
- Other CMSs (Contentful, Strapi, Storyblok, Payload): add the deploy hook URL as a webhook on publish.
- Scheduled rebuilds: call the URL from a cron job, for example every night with
0 3 * * * curl -X POST '<deploy hook URL>', or from a service like cron-job.org. - CI and automation: a GitHub Actions step, Zapier or Make.
Keep the URL private
The deploy hook URL contains the secret: anyone who has it can deploy your site. They can't change your code or content, but they can start builds.
- Don't paste the URL into chats or emails. Slack, Teams, Discord and email security scanners open links to preview or check them, and each visit starts a deploy. Send the header form to services that support it.
- Regenerate in the deploy hook panel replaces the secret. The old URL stops working at once, so update every service that uses it.
- Disable turns the deploy hook off until you create a new one.
Troubleshooting
| Answer | What it means | Fix |
|---|---|---|
403 Invalid deploy hook secret | The secret is wrong, was regenerated, or the hook was disabled | Copy the current URL from Publish ▾ › Deploy hook |
400 No deployment configured for this repository | The site was never deployed | Deploy once from Publish ▾, then try again |
409 The repository owner has no GitHub account linked | Hook deploys run as the owner, who has no GitHub link | The owner links GitHub in the editor |
401 Missing X-Webhook-Secret header | A header-form request without the header | Add the header, or use the deploy hook URL |
| The site deploys when nobody published anything | The URL was opened by a link preview, or leaked | Regenerate, and send the header form where possible |
| The deploy ran but new content is missing | The page is Dynamic, cached, or the CMS cached its API response | Wait for the cache to expire, or check your CMS's API caching |
For build failures, see Deployment Troubleshooting.