Deployment webhooks notify an external URL when your GitHub deployment status changes on WordPress.com. This guide covers how to create, verify, and manage webhooks using the WordPress.com REST API.
You need an active GitHub deployment connection on your WordPress.com site before you can add webhooks. If you haven’t connected a repository yet, follow the steps in Use GitHub deployments on WordPress.com.
You also need your site ID and deployment ID, which you can retrieve through the WordPress.com REST API.
Deployment webhooks send HTTP POST requests to a URL you choose each time a deployment event occurs. You can register up to five webhooks per deployment connection.
To create a webhook:
- Send a POST request to the webhooks endpoint:
POST /wpcom/v2/sites/{site_id}/hosting/code-deployments/{deployment_id}/webhooks
- Include the required parameters in the request body:
url(required): The URL that receives webhook POST requests. Must use HTTP or HTTPS.events(optional): Comma-separated list of events to subscribe to. Defaults to all events if omitted.
- Send the request. Here is an example request body and response:
Request:
{ "url": "https://yourgroovydomain.com/webhook", "events": "completed,failed"}
Response:
{ "success": true, "webhook": { "id": "550e8400-e29b-41d4-a716-446655440000", "url": "https://yourgroovydomain.com/webhook", "secret": "abc123def456ghi789jkl012mno345pqr678stu901vwx234", "events": ["completed", "failed"], "created_at": "2026-03-05T12:00:00+00:00" }, "message": "Webhook added successfully."}
- Save the
secretvalue from the response immediately. The secret is only returned once and you need it to verify webhook signatures.
You can subscribe a webhook to any combination of deployment lifecycle events:
building: The deployment build has started.queued: The deployment is queued and waiting to run.started: The deployment has started running.completed: The deployment completed successfully.failed: The deployment failed.cancelled: The deployment was cancelled.
If you omit the events parameter when creating a webhook, it subscribes to all events.
Each webhook delivery sends an HTTP POST request with a JSON body containing details about the deployment event.
Every delivery includes the following HTTP headers:
Content-Type: Alwaysapplication/json.User-Agent: Identifies the source:WPCOM-Code-Deployments-Webhook/1.0.X-WPCOM-Event: The event that triggered the delivery.X-WPCOM-Delivery-Attempt: The delivery attempt number (1–3).X-WPCOM-Signature: HMAC-SHA256 signature for verifying authenticity.
The JSON body contains the following fields:
{ "delivery_id": "550e8400-e29b-41d4-a716-446655440000", "event": "completed", "deployment_run": { "id": 123, "status": "completed", "created_at": "2026-03-05T12:00:00+00:00", "started_at": "2026-03-05T12:00:05+00:00", "completed_at": "2026-03-05T12:01:30+00:00", "duration_seconds": 85, "failure_code": null }, "deployment": { "id": 456, "target_dir": "/wp-content", "is_automated": true, "workflow_path": ".github/workflows/deploy.yml" }, "site": { "id": 12345678, "name": "My Site", "url": "https://yourgroovydomain.com" }, "repository": { "id": 987654, "name": "owner/repo", "branch": "main", "url": "https://github.com/owner/repo" }, "commit": { "sha": "abc123def456789...", "short_sha": "abc123d", "message": "Update theme styles", "author": { "name": "Jane Doe", "github_id": 12345, "avatar_url": "https://avatars.githubusercontent.com/u/12345", "profile_url": "https://github.com/janedoe" } }, "triggered_by": { "user_id": 67890, "display_name": "Jane Doe" }, "links": { "logs": "https://wordpress.com/github-deployments/yourgroovydomain.com/logs/456", "github_workflow": "https://github.com/owner/repo/actions/runs/789" }}
Some payload fields return null depending on the deployment state:
triggered_byisnullfor automated deployments.deployment_run.duration_secondsisnulluntil the deployment completes.deployment_run.failure_codeisnullunless the deployment failed.links.github_workflowisnullif no GitHub Actions workflow is configured.
Every webhook delivery is signed with the secret returned when you created the webhook. The signature appears in the X-WPCOM-Signature header in the format sha256=<hex-digest>.
To verify a webhook delivery:
- Compute the HMAC-SHA256 hash of the raw request body using your webhook secret as the key.
- Compare the computed hash with the value in the
X-WPCOM-Signatureheader after removing thesha256=prefix. - Reject the request if the values do not match.
The following examples demonstrate signature verification in different languages.
Command line:
echo -n '<raw JSON body>' | openssl dgst -sha256 -hmac '<your webhook secret>'
PHP:
$payload = file_get_contents( 'php://input' );$secret = 'your_webhook_secret';$signature = 'sha256=' . hash_hmac( 'sha256', $payload, $secret );if ( ! hash_equals( $signature, $_SERVER['HTTP_X_WPCOM_SIGNATURE'] ) ) { http_response_code( 403 ); exit( 'Invalid signature' );}
Node.js:
const crypto = require( 'crypto' );function verifySignature( payload, secret, signatureHeader ) { const expected = 'sha256=' + crypto.createHmac( 'sha256', secret ) .update( payload ).digest( 'hex' ); return crypto.timingSafeEqual( Buffer.from( expected ), Buffer.from( signatureHeader ) );}
You can list, update, delete, and test webhooks through the WordPress.com REST API. All endpoints share the same base path:
/wpcom/v2/sites/{site_id}/hosting/code-deployments/{deployment_id}/webhooks
To retrieve all webhooks for a deployment connection, send a GET request to the base path:
GET /wpcom/v2/sites/{site_id}/hosting/code-deployments/{deployment_id}/webhooks
The response includes each webhook’s URL, subscribed events, and delivery statistics. The webhook secret is not included in list responses.
To change a webhook’s URL or subscribed events, send a PUT request:
PUT .../webhooks/{webhook_id}
The endpoint accepts the following parameters:
url(required): The new webhook URL.events(optional): New comma-separated list of events.
You cannot change a webhook’s secret. To rotate a secret, delete the webhook and create a new one.
To remove a webhook, send a DELETE request:
DELETE .../webhooks/{webhook_id}
To send a test delivery to a webhook, send a POST request:
POST .../webhooks/{webhook_id}/test
The test delivery runs immediately and returns the result in the response. It uses a single delivery attempt with sample deployment data, signed with the webhook’s secret.
Example response:
{ "success": true, "delivery_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "status_code": 200, "duration_ms": 145, "error": null}
To view recent deliveries for a webhook, send a GET request:
GET .../webhooks/{webhook_id}/deliveries
The response returns the last 10 deliveries, newest first. Each record includes the event type, HTTP status code, response time, attempt number, and any error message.
Webhook deliveries run in the background and do not delay or block your deployments. The following rules apply to delivery attempts:
- If a delivery fails (non-2xx response or network error), the system retries up to three times with delays of 1, 5, and 15 seconds between attempts.
- Each delivery attempt has a 10-second timeout.
- The last 10 deliveries per webhook are stored and available through the delivery history endpoint.