> ## Documentation Index
> Fetch the complete documentation index at: https://docs.periscope.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Deployment webhook

> Send deployment events from any CI/CD system to Periscope. This is how you unlock DORA metrics.

The deployment webhook is how Periscope receives data about your deployments. You send a standardized JSON payload from your CI/CD pipeline (GitHub Actions, Azure DevOps, Jenkins, GitLab CI, etc.) after each deployment, and Periscope handles the rest.

## Overview

* **Endpoint:** `POST https://app.periscope.sh/api/webhooks/deployments`
* **Authentication:** Bearer token (API key from Periscope settings)
* **Format:** JSON
* **Idempotent:** Duplicate `deploymentId` values update the existing record

## Setup

<Steps>
  <Step title="Generate an API key">
    Go to **Settings > Deployments** in the Periscope dashboard and click **Generate API Key**. The key is scoped to your organization.

    <Warning>Copy the key immediately — you will not be able to view it again after closing the dialog.</Warning>
  </Step>

  <Step title="Store the key as a secret">
    Add the API key as a secret in your CI/CD system:

    * **GitHub Actions:** Repository or organization secret
    * **Azure DevOps:** Pipeline variable (secret)
    * **Jenkins:** Credentials store
    * **GitLab CI:** CI/CD variable (masked)
  </Step>

  <Step title="Add the webhook call to your pipeline">
    Add an HTTP POST after your deployment step completes. See the [CI/CD examples](#cicd-examples) below for platform-specific snippets.
  </Step>
</Steps>

## Payload schema

### Required fields

| Field          | Type     | Description                                                                                                                                                          |
| -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `deploymentId` | `string` | Unique identifier from your CI/CD system (e.g., run ID). Used for idempotency.                                                                                       |
| `commitSha`    | `string` | The git commit SHA that was deployed. **This is the key field** — it links deployments to merged PRs for lead time calculation. Should be the full 40-character SHA. |
| `environment`  | `string` | Target environment: `"production"`, `"staging"`, `"development"`, `"preview"`, `"test"`, or any custom value.                                                        |
| `status`       | `string` | Deployment status: `"pending"`, `"in_progress"`, `"success"`, `"failure"`, `"cancelled"`, or `"rolled_back"`.                                                        |
| `startedAt`    | `string` | ISO 8601 timestamp of when the deployment started.                                                                                                                   |

### Optional fields

| Field               | Type     | Description                                                                                                                        |
| ------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `organizationSlug`  | `string` | Your org slug. Optional — the org is resolved from the API key. If provided, it is validated as a safety check.                    |
| `completedAt`       | `string` | ISO 8601 timestamp. **Required** when status is `"success"`, `"failure"`, `"cancelled"`, or `"rolled_back"`.                       |
| `service`           | `string` | Name of the service being deployed. Periscope matches this to a repository by name. Used for per-service metrics and MTTR pairing. |
| `branch`            | `string` | The branch that was deployed.                                                                                                      |
| `previousCommitSha` | `string` | The commit SHA of the previous deployment to this environment. Enables Periscope to link all PRs merged between the two commits.   |
| `triggeredBy`       | `string` | Who or what triggered the deployment (username, email, `"automation"`, `"schedule"`).                                              |
| `pipelineName`      | `string` | Name of the CI/CD pipeline or workflow.                                                                                            |
| `pipelineUrl`       | `string` | URL to view this deployment in your CI/CD system. Displayed in the dashboard for quick navigation.                                 |
| `rawPayload`        | `object` | The original payload from your CI/CD system, stored for reference.                                                                 |
| `metadata`          | `object` | Custom key-value pairs (`string`, `number`, or `boolean` values). Use for version numbers, feature flags, release notes, etc.      |

<Info>
  The `commitSha` field is the most important optional detail beyond the required fields. Without an accurate commit SHA, Periscope cannot link deployments to PRs, and lead time data will be empty.
</Info>

## Example payloads

<AccordionGroup>
  <Accordion title="Minimal (required fields only)">
    ```json theme={"system"}
    {
      "deploymentId": "azure-devops-run-12345",
      "commitSha": "a1b2c3d4e5f60ab2c3d4e5f60ab2c3d4e5f60ab2",
      "environment": "production",
      "status": "success",
      "startedAt": "2024-01-15T10:30:00Z",
      "completedAt": "2024-01-15T10:35:00Z"
    }
    ```
  </Accordion>

  <Accordion title="Complete (all fields)">
    ```json theme={"system"}
    {
      "deploymentId": "azure-devops-run-12345",
      "commitSha": "a1b2c3d4e5f60ab2c3d4e5f60ab2c3d4e5f60ab2",
      "environment": "production",
      "status": "success",
      "startedAt": "2024-01-15T10:30:00Z",
      "completedAt": "2024-01-15T10:35:00Z",
      "service": "api-gateway",
      "branch": "main",
      "previousCommitSha": "f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0",
      "triggeredBy": "alice@company.com",
      "pipelineName": "Production Deploy",
      "pipelineUrl": "https://dev.azure.com/org/project/_build/results?buildId=12345",
      "metadata": {
        "version": "2.1.0",
        "releaseNotes": "Bug fixes and performance improvements",
        "featureFlags": "new-checkout-enabled"
      }
    }
    ```
  </Accordion>

  <Accordion title="In-progress deployment">
    ```json theme={"system"}
    {
      "deploymentId": "github-actions-run-67890",
      "commitSha": "b2c3d4e5f60ab2c3d4e5f60ab2c3d4e5f60ab2c3",
      "environment": "staging",
      "status": "in_progress",
      "startedAt": "2024-01-15T11:00:00Z",
      "service": "web-frontend",
      "branch": "release/v2.2",
      "triggeredBy": "automation",
      "pipelineName": "Staging Deploy"
    }
    ```
  </Accordion>

  <Accordion title="Failed deployment">
    ```json theme={"system"}
    {
      "deploymentId": "jenkins-build-99999",
      "commitSha": "c3d4e5f60ab2c3d4e5f60ab2c3d4e5f60ab2c3d4",
      "environment": "production",
      "status": "failure",
      "startedAt": "2024-01-15T12:00:00Z",
      "completedAt": "2024-01-15T12:05:00Z",
      "service": "payment-service",
      "triggeredBy": "bob",
      "pipelineUrl": "https://jenkins.company.com/job/deploy/99999",
      "metadata": {
        "errorCode": "HEALTH_CHECK_FAILED",
        "rollbackInitiated": true
      }
    }
    ```
  </Accordion>
</AccordionGroup>

## CI/CD examples

### GitHub Actions

```yaml theme={"system"}
name: Deploy and notify Periscope

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Your deployment steps here...

      - name: Notify Periscope
        if: always()
        run: |
          STATUS="${{ job.status == 'success' && 'success' || 'failure' }}"
          curl -s -X POST https://app.periscope.sh/api/webhooks/deployments \
            -H "Authorization: Bearer ${{ secrets.PERISCOPE_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d "{
              \"deploymentId\": \"gha-${{ github.run_id }}\",
              \"commitSha\": \"${{ github.sha }}\",
              \"environment\": \"production\",
              \"status\": \"$STATUS\",
              \"startedAt\": \"$(date -u -d '${{ github.event.head_commit.timestamp }}' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u '+%Y-%m-%dT%H:%M:%SZ')\",
              \"completedAt\": \"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\",
              \"service\": \"${{ github.event.repository.name }}\",
              \"branch\": \"${{ github.ref_name }}\",
              \"triggeredBy\": \"${{ github.actor }}\",
              \"pipelineName\": \"${{ github.workflow }}\",
              \"pipelineUrl\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
            }"
```

### Azure DevOps

```yaml theme={"system"}
# Add as the final stage in your pipeline
- stage: NotifyPeriscope
  condition: always()
  jobs:
    - job: Notify
      steps:
        - task: Bash@3
          displayName: "Notify Periscope"
          inputs:
            targetType: inline
            script: |
              if [ "$(Agent.JobStatus)" = "Succeeded" ]; then
                STATUS="success"
              else
                STATUS="failure"
              fi

              curl -s -X POST https://app.periscope.sh/api/webhooks/deployments \
                -H "Authorization: Bearer $(PERISCOPE_API_KEY)" \
                -H "Content-Type: application/json" \
                -d "{
                  \"deploymentId\": \"ado-$(Build.BuildId)\",
                  \"commitSha\": \"$(Build.SourceVersion)\",
                  \"environment\": \"production\",
                  \"status\": \"$STATUS\",
                  \"startedAt\": \"$(Build.QueuedTime)\",
                  \"completedAt\": \"$(date -u '+%Y-%m-%dT%H:%M:%SZ')\",
                  \"service\": \"$(Build.Repository.Name)\",
                  \"branch\": \"$(Build.SourceBranchName)\",
                  \"triggeredBy\": \"$(Build.RequestedFor)\",
                  \"pipelineName\": \"$(Build.DefinitionName)\",
                  \"pipelineUrl\": \"$(System.CollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId)\"
                }"
```

### Generic curl

```bash theme={"system"}
curl -X POST https://app.periscope.sh/api/webhooks/deployments \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "deploymentId": "deploy-12345",
    "commitSha": "a1b2c3d4e5f60ab2c3d4e5f60ab2c3d4e5f60ab2",
    "environment": "production",
    "status": "success",
    "startedAt": "2024-01-15T10:30:00Z",
    "completedAt": "2024-01-15T10:35:00Z",
    "service": "my-service"
  }'
```

## Validation rules

The webhook validates your payload before processing:

* `deploymentId`, `commitSha`, `environment`, `status`, and `startedAt` are all required strings
* `commitSha` must be 5-40 hexadecimal characters
* `status` must be one of: `pending`, `in_progress`, `success`, `failure`, `cancelled`, `rolled_back`
* `startedAt` and `completedAt` must be valid ISO 8601 dates
* `completedAt` is **required** when status is a terminal state (`success`, `failure`, `cancelled`, `rolled_back`)

## Idempotency

The `deploymentId` field is used for idempotency. If you send a payload with a `deploymentId` that already exists for your organization, Periscope updates the existing deployment record rather than creating a duplicate.

This is useful for:

* Sending an initial `in_progress` event, then updating with `success` or `failure` when the deployment completes
* Safely retrying failed webhook calls without worrying about duplicates

## PR linking

Periscope automatically links deployments to merged PRs using the `commitSha` in your payload. This is what powers the [lead time for changes](/metrics/lead-time) metric. Providing the `service` field improves matching accuracy.

## Free trial

If your organization is on the Free plan, sending your first deployment event starts a 14-day trial of Business features — including all DORA metrics, the MCP server, and AI-powered risk signals.

## Response format

**Success (201 Created):**

```json theme={"system"}
{
  "action": "created",
  "deploymentId": 42,
  "linkedPRs": 3
}
```

**Update (200 OK):**

```json theme={"system"}
{
  "action": "updated",
  "deploymentId": 42,
  "linkedPRs": 3
}
```

**Validation error (400):**

```json theme={"system"}
{
  "error": "Invalid payload: commitSha must be a valid hex string (5-40 characters)"
}
```

**Authentication error (401):**

```json theme={"system"}
{
  "error": "Invalid or missing API key"
}
```

<Card title="API reference" icon="book" href="/api-reference/ingest-deployment">
  See the full API reference for the deployment webhook endpoint.
</Card>
