Guides

Deploy hooks

Give a project its own URL that redeploys it (or runs a job) when called, from CI, a registry, a cron service or a script. Covers token, HMAC and open authentication, service selection, responses and the delivery log.

A deploy hook (the panel calls it an incoming webhook) is a URL that belongs to one project. When something calls it with the right credential, Synq starts a deploy of that project, or runs one of your jobs. Each hook has its own address and its own secret, and you can have several per project (one for CI, one for a registry, and so on).

Use a deploy hook when the thing that knows "it is time to deploy" is not a Git push Synq already receives:

  • CI finished building and pushed a new image to :main.
  • A pasted Dockerfile or Compose project has no repository to push to.
  • A headless CMS published content and a static site must rebuild.
  • A scheduler outside Synq should redeploy nightly.

For "deploy when I push to the repository", use auto-deploy instead.

Create a hook

Open the Webhooks tab

Open the project, then Source & Triggers → Webhooks. The Incoming webhooks card ("Trigger a deploy or a job from an external URL. Each webhook gets its own address and its own auth.") lists the project's hooks. Press New webhook.

Name it and choose the action

  • Name: anything that tells you who calls it, for example "CI deploy hook".
  • Action:
    • Deploy: "Redeploy this project or selected services when called."
    • Run a job: "Run one of your jobs when called." Pick the Job.
  • Services (optional), for Deploy: leave Whole project checked to redeploy everything, or tick the services that should redeploy together (for a Compose or multi-service project).

Choose how callers authenticate

  • Token (default): a secret bearer token. The caller sends Authorization: Bearer <token>.
  • HMAC signature: the caller signs the request body with a shared secret and sends the signature in X-Signature-256. The secret itself never travels.
  • Open: no auth. Anyone with the URL can trigger it. Use it only when the caller cannot send a header or sign, and treat the URL itself as a secret. A Run a job hook cannot be open.

Save and copy the URL and secret

The hook appears in the list with its Delivery URL and, for Token and HMAC, its Secret (masked; press Reveal to show it). Copy both into the tool that will call the hook. The row also says what the hook does ("Redeploys this project", "Redeploys 1 selected service", "Runs the backup job") and shows an auth badge: Token, HMAC or Open.

The delivery URL looks like this on an install whose panel is at https://app.example.com:

https://app.example.com/api/proxy/api/webhooks/incoming/<hook-id>

It uses the instance's public URL (SYNQ_PUBLIC_URL). An install without a public URL shows a local address that outside services cannot reach; set a public URL first.

Call a hook

A hook accepts POST with any body up to 5 MB. The body is ignored by the action; with HMAC it is what you sign.

Token

curl -X POST \
  -H "Authorization: Bearer $SYNQ_HOOK_TOKEN" \
  https://app.example.com/api/proxy/api/webhooks/incoming/<hook-id>

Only the Authorization header is read. A token in the query string (?token=) is not accepted, even though the form's hint mentions it.

HMAC signature

Sign the exact bytes you send with HMAC-SHA256 and the hook's secret, hex-encode the result, and send it as sha256=<hex> in X-Signature-256 (X-Hub-Signature-256 and X-Signature are also read):

BODY='{"ref":"main","sha":"4f2c1e9"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SYNQ_HOOK_SECRET" | sed 's/^.* //')
curl -X POST \
  -H "Content-Type: application/json" \
  -H "X-Signature-256: sha256=$SIG" \
  --data "$BODY" \
  https://app.example.com/api/proxy/api/webhooks/incoming/<hook-id>

The sha256= prefix is required. Because the format matches GitHub's X-Hub-Signature-256, a GitHub repository or organization webhook (content type application/json, secret set to the hook's secret) can call an HMAC hook directly.

From CI

A GitHub Actions step at the end of a workflow that pushed a new image:

- name: Deploy on Synq
  run: |
    curl --fail -X POST \
      -H "Authorization: Bearer ${{ secrets.SYNQ_HOOK_TOKEN }}" \
      "${{ secrets.SYNQ_HOOK_URL }}"

GitLab CI:

deploy:
  stage: deploy
  script:
    - 'curl --fail -X POST -H "Authorization: Bearer $SYNQ_HOOK_TOKEN" "$SYNQ_HOOK_URL"'

Responses

StatusBodyMeaning
200{"data":{"action":"deploy","ref":"<deployment-id>"}}Started. ref is the deployment ID (or the job run ID for a job hook).
404{"error":"Not found"}No such hook, the hook is disabled, or the credential is wrong. These all look the same on purpose, so the URL cannot be probed.
502{"error":"Action failed"}The credential was fine but the action could not start (for example a selected service no longer exists). The reason is in the delivery log.
429More than 120 webhook calls in a minute from the same IP address.

What a deploy hook deploys

  • It deploys the project's current settings, like Redeploy on the Deployments tab: the latest commit of the deploy branch for a repository, the stored files for an upload, the configured image for an image project.
  • It pulls registry images again instead of reusing the copy on the server, so a new push to the same tag (:main, :latest) is picked up.
  • With services selected, exactly those services redeploy together, and the call fails (instead of deploying more) if one of them was deleted.
  • The deploy runs with the permissions of the person who created the hook. If that person loses write access to the project, the hook stops working until someone with access edits or recreates it.

Manage hooks

Each row in Incoming webhooks has:

  • a switch to enable or disable the hook (a disabled hook answers 404);
  • Rotate secret: a new token or HMAC secret. The old one stops working immediately, so update the caller with the new value;
  • Delete: the URL stops working immediately.

Delivery log

Below the hooks, Recent deliveries ("GitHub pushes and custom hook calls received for this project") lists every call newest first, with Load more for older ones. Each row shows the outcome:

OutcomeMeaning
dispatchedThe deploy or job started.
skippedNothing to do, for example a push to another branch or a commit that is already live.
ignoredAn event that never deploys, such as a provider's ping or a non-push event.
failedSomething went wrong. The error is shown on the row.

The same log includes push deliveries from GitHub and from Git URL providers. Calls rejected for a bad credential are recorded in the organization's audit log, not here.

API

RouteWhat it does
GET /api/projects/:id/incoming-webhooksList a project's hooks.
POST /api/projects/:id/incoming-webhooksCreate one: name, actionType (deploy or job), actionConfig (serviceIds, or jobKey), authMode (token, hmac or none). The response includes the secret.
PATCH /api/projects/:id/incoming-webhooks/:hookIdRename, enable or disable, or change the action or auth.
POST /api/projects/:id/incoming-webhooks/:hookId/rotateNew secret.
DELETE /api/projects/:id/incoming-webhooks/:hookIdDelete.
GET /api/projects/:id/webhook-deliveriesThe project's delivery log.
POST /api/webhooks/incoming/:hookIdThe public trigger (what the delivery URL points at).

On this page