API Reference
Webhooks

Webhooks

Outgoing webhooks let you receive real-time HTTP notifications when events happen in your oec.sh environments — deployments complete, environments are created, statuses change. Instead of polling, your server receives a push.

Base URL: https://api.oec.sh/api/public/v1


Available Events

Deployment Events

EventWhen it fires
deploy.startedA deployment task begins executing.
deploy.completedA deployment finishes successfully.
deploy.failedA deployment finishes with an error.
deploy.cancelledA deployment is cancelled before completion.

Environment Events

EventWhen it fires
environment.createdA new environment is provisioned.
environment.deletedAn environment is deleted.
environment.status_changedAn environment's status transitions (e.g. stoppedrunning).

Automation Rule Events

EventWhen it fires
automation_rule.triggeredAn automation rule matched its trigger condition and began executing.
automation_rule.completedAn automation rule ran all its actions successfully.
automation_rule.failedAn automation rule failed during execution (e.g. deploy error, action timeout).

You can subscribe to up to 10 events per webhook.


Setup

List Webhooks

GET /webhooks

Returns all webhooks for your organisation (or project, if using a project-scoped key).

Query Parameters

ParameterTypeDescription
limitintegerResults per page. Min 1, max 100. Default 50.
offsetintegerSkip this many results. Default 0.

Response — 200 OK

{
  "items": [
    {
      "id": "wh-uuid",
      "url": "https://hooks.example.com/oec-events",
      "events": ["deploy.completed", "deploy.failed"],
      "description": "CI/CD notifications",
      "project_id": null,
      "is_active": true,
      "last_triggered_at": "2026-03-01T14:22:00Z",
      "failure_count": 0,
      "created_at": "2026-01-15T09:00:00Z"
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0,
  "has_more": false
}

Create Webhook

POST /webhooks

Registers a new outgoing webhook. Requires a full_access API key.

Secret shown once. The response includes a secret field (format: whsec_...) for HMAC signature verification. This value is shown only at creation time. Store it securely. If lost, rotate it with POST /webhooks/{id}/rotate-secret.

This endpoint supports idempotency via the X-Idempotency-Key header. Duplicate requests within 24 hours return the original response.

Limits

  • Max 100 webhooks per organisation
  • Webhook mutation rate limit: 10 create/update/delete per minute per API key

Request Body

FieldTypeRequiredDescription
urlstringYesHTTPS endpoint to deliver events to. Max 2048 characters. Must use HTTPS. Private IP literals are rejected (SSRF protection).
eventsarrayYesList of event names to subscribe to. See Available Events. Max 10 events.
descriptionstringNoOptional note for your reference. Max 500 characters.
project_idUUIDNoScope the webhook to a single project. Omit for org-wide events.
is_activebooleanNoWhether to deliver events immediately. Defaults to true.
formatstringNoPayload format. One of raw (default), slack, teams, or discord. See Payload Formats.

Response — 201 Created

{
  "id": "wh-uuid",
  "url": "https://hooks.example.com/oec-events",
  "events": ["deploy.completed", "deploy.failed"],
  "description": "CI/CD notifications",
  "project_id": null,
  "is_active": true,
  "last_triggered_at": null,
  "failure_count": 0,
  "created_at": "2026-03-13T10:00:00Z",
  "secret": "whsec_abc123..."
}

Example

curl -X POST "https://api.oec.sh/api/public/v1/webhooks" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: $(uuidgen)" \
  -d '{
    "url": "https://hooks.example.com/oec-events",
    "events": ["deploy.completed", "deploy.failed", "environment.created"],
    "description": "CI/CD pipeline notifications"
  }'

Get Webhook

GET /webhooks/{webhook_id}

Returns a single webhook by ID.

Update Webhook

PATCH /webhooks/{webhook_id}

Updates a webhook's URL, events, description, or active state. Requires full_access key. All fields are optional.

Request Body

FieldTypeDescription
urlstringNew HTTPS endpoint URL.
eventsarrayNew event list (replaces existing). Max 10.
descriptionstringNew description.
is_activebooleanSet to true to re-enable a paused webhook.
formatstringChange the payload format: raw, slack, teams, or discord.

Example — Re-enable a paused webhook

curl -X PATCH "https://api.oec.sh/api/public/v1/webhooks/YOUR_WEBHOOK_ID" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"is_active": true}'

Delete Webhook

DELETE /webhooks/{webhook_id}
⚠️

Permanently deletes the webhook and all its delivery history. This cannot be undone.

Requires full_access key. Returns 204 No Content.

curl -X DELETE "https://api.oec.sh/api/public/v1/webhooks/YOUR_WEBHOOK_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"

Test a Webhook

POST /webhooks/{webhook_id}/test

Sends a synchronous test ping event to the webhook URL and returns the HTTP response. Does not create a delivery log entry. Requires full_access key.

curl -X POST "https://api.oec.sh/api/public/v1/webhooks/YOUR_WEBHOOK_ID/test" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "success": true,
  "status_code": 200,
  "duration_ms": 142,
  "message": null
}

Rotate Signing Secret

POST /webhooks/{webhook_id}/rotate-secret

Generates a new HMAC signing secret. The old secret is immediately invalidated. The new plaintext secret is shown once in the response — store it immediately.

curl -X POST "https://api.oec.sh/api/public/v1/webhooks/YOUR_WEBHOOK_ID/rotate-secret" \
  -H "Authorization: Bearer YOUR_API_KEY"
{
  "secret": "whsec_new_secret_here..."
}

Payload Formats

oec.sh can deliver event payloads in four formats. Choose the format that matches your destination when creating or updating a webhook.

FormatUse when...
rawYou control the receiver — a custom server, CI pipeline, or automation script. Full JSON envelope with HMAC signature.
slackSending directly to a Slack Incoming Webhook URL (Block Kit format, no HMAC header).
teamsSending to a Microsoft Teams Incoming Webhook (Adaptive Card format).
discordSending to a Discord webhook URL (Embed format).

raw (default)

The full event envelope is delivered as JSON with the X-OEC-Signature HMAC header. See Signature Verification. This is the most flexible format — you can parse and forward however you like.

{
  "event_id": "evt_01abc123def456",
  "event": "automation_rule.completed",
  "timestamp": "2026-03-15T09:00:00Z",
  "organization_id": "YOUR_ORG_ID",
  "data": {
    "rule_name": "Deploy on push",
    "action_type": "deploy_latest",
    "trigger_type": "push",
    "branch": "main",
    "actor": "jane@example.com"
  }
}

slack

Delivers a Slack Block Kit (opens in a new tab) message directly to a Slack Incoming Webhook URL. The X-OEC-Signature header is not sent for platform formats — Slack handles its own auth.

To use:

  1. Create an Incoming Webhook in Slack (your workspace → Apps → Incoming Webhooks)
  2. Copy the Webhook URL (https://hooks.slack.com/services/...)
  3. Create a webhook on oec.sh with that URL and "format": "slack"

The message includes the event name, a color-coded sidebar (green for success, red for failure), and key details from the event data.

teams

Delivers an Adaptive Card (opens in a new tab) to a Microsoft Teams channel via an Incoming Webhook connector. Set up a Teams Incoming Webhook, paste the URL, and set "format": "teams".

discord

Delivers a rich embed to a Discord channel webhook. In Discord, go to channel Settings → Integrations → Webhooks, create one, and copy the URL. Set "format": "discord".

Platform formats (slack, teams, discord) do not include the X-OEC-Signature header — these services use their own authentication. If you need signature verification, use raw format with your own endpoint.


Receiving Webhooks

Payload Format

oec.sh delivers a POST request to your endpoint with a JSON body:

{
  "event_id": "evt_01abc123def456",
  "event": "deploy.completed",
  "timestamp": "2026-03-13T12:05:00Z",
  "organization_id": "YOUR_ORG_ID",
  "data": {
    "environment_id": "YOUR_ENV_ID",
    "project_id": "YOUR_PROJECT_ID",
    "task_id": "task-uuid",
    "status": "completed",
    "duration_seconds": 300
  }
}
💡

Each payload includes a unique event_id. Use this field to deduplicate deliveries in your handler — retries send the same event_id.

Signature Verification

Every delivery includes an X-OEC-Signature header:

X-OEC-Signature: sha256=<hex_digest>

The signature is an HMAC-SHA256 of the raw request body, signed with your webhook's secret.

Always verify signatures before processing a webhook payload. This prevents bad actors from sending fake events to your endpoint.

import hashlib
import hmac
from fastapi import Request, HTTPException
 
WEBHOOK_SECRET = "whsec_your_secret_here"
 
async def verify_webhook(request: Request) -> bytes:
    body = await request.body()
    signature_header = request.headers.get("X-OEC-Signature", "")
 
    if not signature_header.startswith("sha256="):
        raise HTTPException(status_code=400, detail="Missing signature")
 
    expected_sig = signature_header[len("sha256="):]
    computed_sig = hmac.new(
        WEBHOOK_SECRET.encode(),
        body,
        hashlib.sha256,
    ).hexdigest()
 
    if not hmac.compare_digest(computed_sig, expected_sig):
        raise HTTPException(status_code=401, detail="Invalid signature")
 
    return body
⚠️

Always use a timing-safe comparison (e.g. hmac.compare_digest in Python, crypto.timingSafeEqual in Node.js) to avoid timing oracle attacks. Never use == to compare signatures.

Responding to Webhooks

Your endpoint must respond with an HTTP 2xx status code within 10 seconds. Any non-2xx response or a timeout is recorded as a delivery failure.


Reliability

Retry Logic

If your endpoint returns a non-2xx response or times out, oec.sh retries delivery with exponential backoff:

AttemptDelay
1 (initial)Immediate
2~30 seconds
3~5 minutes
4~30 minutes
5~2 hours

Auto-Pause on Repeated Failures

After 10 consecutive failed deliveries, the webhook is automatically paused (is_active set to false). You will need to:

  1. Fix the issue with your endpoint
  2. Re-enable the webhook:
    curl -X PATCH "https://api.oec.sh/api/public/v1/webhooks/YOUR_WEBHOOK_ID" \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"is_active": true}'

Delivery Log

GET /webhooks/{webhook_id}/deliveries

Returns the 50 most recent delivery attempts for a webhook, newest first.

[
  {
    "id": "delivery-uuid",
    "event": "deploy.completed",
    "status": "delivered",
    "attempt_count": 1,
    "response_status": 200,
    "response_body": null,
    "error_reason": null,
    "duration_ms": 142,
    "delivered_at": "2026-03-13T12:05:01Z",
    "created_at": "2026-03-13T12:05:00Z"
  }
]

Delivery Object Fields

FieldTypeDescription
idUUIDDelivery record ID.
eventstringEvent name.
statusstring"delivered", "failed", or "retrying".
attempt_countintegerHow many times delivery was attempted.
response_statusinteger|nullHTTP status code from your endpoint.
response_bodystring|nullResponse body snippet (when available).
error_reasonstring|nullHuman-readable reason for failure, if applicable.
duration_msinteger|nullRound-trip time to your endpoint.
delivered_atISO 8601|nullWhen the most recent attempt completed.
created_atISO 8601When the delivery was first attempted.

Mutation Rate Limit

Webhook create, update, and delete operations are rate-limited to 10 mutations per 60-second window per API key. Exceeding this returns 429 Too Many Requests.