Skip to main content

Webhooks

Pi pushes job.completed and job.failed events to your registered HTTPS endpoints when async jobs reach a terminal state. This lets automation platforms (n8n, agent runners, internal pipelines) react immediately without polling.
Pi signs every delivery with an HMAC-SHA256 signature in the X-Pi-Signature header. Verify the signature on your server before processing any payload. Make your webhook handler idempotent using the evt_pi_* event ID to deduplicate retries.

List webhooks

GET /api/v1/webhooks Returns all webhooks registered for the authenticated organization.

Request

curl -X GET "$PI_BASE_URL/api/v1/webhooks" \
  -H "Authorization: Bearer $PI_API_KEY"

Response fields

data.object
string
Always "list".
data.data
array
Array of webhook objects. Each item includes:

Register a webhook

POST /api/v1/webhooks Registers a new webhook endpoint for the authenticated organization.
endpoint_url must use HTTPS. HTTP URLs are rejected. Your shared secret is used to sign delivery payloads — store it securely and rotate it if it is ever exposed.

Request

curl -X POST "$PI_BASE_URL/api/v1/webhooks" \
  -H "Authorization: Bearer $PI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "endpoint_url": "https://example.com/pi/webhooks",
    "secret": "whsec_..."
  }'

Body parameters

endpoint_url
string
required
The HTTPS URL where Pi should deliver events. Must be a valid URL using the https:// scheme.
secret
string
required
Shared secret used to sign delivery payloads with HMAC-SHA256. Minimum 8 characters, maximum 500 characters.

Response fields

data.object
string
Always "webhook".
data.id
string
UUID of the newly registered webhook.
data.endpoint_url
string
The registered URL.
data.is_active
boolean
true for newly registered webhooks.
data.created_at
string
ISO 8601 creation timestamp.
data.updated_at
string
ISO 8601 last-updated timestamp.

Enable or disable a webhook

PATCH /api/v1/webhooks/:id Toggle a webhook on or off without deleting the registration.

Path parameters

id
string
required
UUID of the webhook to update.

Request

# Disable
curl -X PATCH "$PI_BASE_URL/api/v1/webhooks/<webhook_id>" \
  -H "Authorization: Bearer $PI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"is_active": false}'

# Re-enable
curl -X PATCH "$PI_BASE_URL/api/v1/webhooks/<webhook_id>" \
  -H "Authorization: Bearer $PI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"is_active": true}'

Body parameters

is_active
boolean
Set to false to disable or true to re-enable the webhook.

Delivery

Event types

Pi delivers these event types to registered webhooks:
EventTrigger
job.completedAn async job reached the completed terminal state
job.failedAn async job reached the failed terminal state

Delivery headers

HeaderValue
X-Pi-EventEvent type: job.completed or job.failed
X-Pi-Signaturesha256=<hex> — HMAC-SHA256 signature of the raw JSON body using your webhook secret

Payload shape

{
  "id": "evt_pi_...",
  "object": "event",
  "type": "job.completed",
  "created_at": 1760000000,
  "data": {
    "job": {
      "id": "<job_id>",
      "type": "avatar_generation",
      "status": "completed",
      "result_url": null,
      "error_log": null,
      "payload": {
        "phase": "completed",
        "image_url": "https://...png",
        "input": {
          "prompt": "...",
          "reference_image_count": 0,
          "client_reference_id": "...",
          "metadata": { "workflow": "..." }
        }
      }
    }
  }
}

Signature verification (TypeScript / Node.js)

Verify the X-Pi-Signature header before processing any payload:
import crypto from "crypto";

export function verifyPiWebhookSignature(params: {
  secret: string;
  rawBody: string;
  signatureHeader: string | null;
}): boolean {
  if (!params.signatureHeader) return false;
  if (!params.signatureHeader.startsWith("sha256=")) return false;

  const expected = crypto
    .createHmac("sha256", params.secret)
    .update(params.rawBody)
    .digest("hex");

  const received = params.signatureHeader.slice("sha256=".length);

  // Use timing-safe comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(received)
  );
}
Use rawBody — the unparsed request body string — not a re-serialized JSON object. Parsing and re-serializing can change whitespace or key order and invalidate the signature.

Retry behavior

Pi retries delivery on non-2xx responses. Respond with 2xx as quickly as possible (before any slow processing) and handle the event asynchronously. Use the evt_pi_* event id to deduplicate retries in your handler.