Rivya AI 문서

API Webhooks

signed Rivya API webhook endpoints를 만들고, delivery signatures를 검증하고, delivery attempts를 확인하고, safe test events를 보내세요.

최근 검토일 2026/05/11

Public API generation이 terminal state에 도달한 뒤 Rivya가 서버에 알려야 하는 integration에는 API webhooks를 사용하세요.

GET /api/v1/generations/{taskId} polling도 계속 지원됩니다. Webhooks는 event delivery를 선호하는 production systems에 signed callbacks를 추가합니다.

Required Scope

Webhook management에는 다음 scope가 있는 API key가 필요합니다.

webhooks:manage

Settings에서 새로 만든 key에는 이 scope가 기본으로 포함됩니다.

Endpoint 만들기

POST /api/v1/webhooks
curl https://rivya.ai/api/v1/webhooks \
  -H "Authorization: Bearer rvya_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production webhook",
    "url": "https://example.com/rivya/webhook",
    "event_types": ["generation.succeeded", "generation.failed"]
  }'

response에는 signing_secret이 한 번만 포함됩니다.

{
  "id": "whend_...",
  "object": "webhook_endpoint",
  "name": "Production webhook",
  "url": "https://example.com/rivya/webhook",
  "event_types": ["generation.succeeded", "generation.failed"],
  "status": "active",
  "secret_preview": "whsec_12...abc123",
  "signing_secret": "whsec_...",
  "last_success_at": null,
  "last_failure_at": null,
  "failure_count": 0,
  "created_at": "2026-05-11T00:00:00.000Z",
  "updated_at": "2026-05-11T00:00:00.000Z",
  "disabled_at": null,
  "revoked_at": null
}

전체 secret은 서버에 저장하세요. 잃어버리면 rotate endpoint를 호출하고 receiver를 업데이트해야 합니다.

URL Rules

Endpoint URLs는 HTTPS여야 합니다. Rivya는 credentials, fragments, localhost names, local network addresses, private IP ranges, loopback addresses, reserved addresses가 있는 URLs를 거부합니다.

Rivya는 항상 다음을 보냅니다.

  • POST
  • Content-Type: application/json
  • custom user-controlled request headers 없음
  • automatic redirect following 없음
  • 짧은 delivery timeout

Events

현재 event types:

  • generation.succeeded
  • generation.failed

Webhook payloads는 status endpoint와 같은 public generation serializer를 사용합니다.

{
  "id": "evt_...",
  "type": "generation.succeeded",
  "api_version": "2026-05-11",
  "created_at": "2026-05-11T00:00:00.000Z",
  "data": {
    "generation": {
      "id": "task_public_id",
      "status": "succeeded",
      "model": "z-image",
      "reserved_credits": 1,
      "final_credits": 1,
      "created_at": "2026-05-11T00:00:00.000Z",
      "updated_at": "2026-05-11T00:01:00.000Z",
      "result": {
        "primary_url": "https://...",
        "urls": ["https://..."]
      },
      "error": null
    }
  }
}

Delivery Headers

각 delivery에는 다음이 포함됩니다.

Rivya-Webhook-Id: evt_...
Rivya-Webhook-Timestamp: 1778467200
Rivya-Webhook-Signature: v1=<hex-hmac-sha256>
Rivya-Webhook-Attempt: 1
Rivya-Webhook-Endpoint-Id: whend_...
Rivya-Request-Id: req_...

Signature input:

${timestamp}.${rawBody}

Algorithm:

HMAC-SHA256 with the endpoint signing secret

stale timestamps가 있는 requests는 거부하세요. 5분 tolerance가 실용적인 기본값입니다.

JavaScript Verification

import crypto from "node:crypto";

function verifyRivyaWebhook({ rawBody, headers, signingSecret }) {
  const timestamp = headers["rivya-webhook-timestamp"];
  const signature = headers["rivya-webhook-signature"] || "";
  const actual = signature.split(",").find((part) => part.startsWith("v1="))?.slice(3);
  const expected = crypto
    .createHmac("sha256", signingSecret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  if (!actual) return false;
  return crypto.timingSafeEqual(Buffer.from(actual, "hex"), Buffer.from(expected, "hex"));
}

Python Verification

import hmac
import hashlib

def verify_rivya_webhook(raw_body: str, headers: dict, signing_secret: str) -> bool:
    timestamp = headers.get("rivya-webhook-timestamp", "")
    signature = headers.get("rivya-webhook-signature", "")
    actual = next((part[3:] for part in signature.split(",") if part.startswith("v1=")), "")
    expected = hmac.new(
        signing_secret.encode(),
        f"{timestamp}.{raw_body}".encode(),
        hashlib.sha256,
    ).hexdigest()
    return bool(actual) and hmac.compare_digest(actual, expected)

Endpoints 관리

GET /api/v1/webhooks
GET /api/v1/webhooks/{endpointId}
PATCH /api/v1/webhooks/{endpointId}
DELETE /api/v1/webhooks/{endpointId}
POST /api/v1/webhooks/{endpointId}/rotate-secret
curl https://rivya.ai/api/v1/webhooks \
  -H "Authorization: Bearer rvya_sk_..."

curl https://rivya.ai/api/v1/webhooks/whend_... \
  -H "Authorization: Bearer rvya_sk_..."

curl -X PATCH https://rivya.ai/api/v1/webhooks/whend_... \
  -H "Authorization: Bearer rvya_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"status":"disabled"}'

curl -X POST https://rivya.ai/api/v1/webhooks/whend_.../rotate-secret \
  -H "Authorization: Bearer rvya_sk_..."

DELETE /api/v1/webhooks/{endpointId}는 endpoint를 비활성화합니다. delivery history는 삭제하지 않습니다.

Delivery Records

최근 events 나열:

GET /api/v1/webhook-events
curl https://rivya.ai/api/v1/webhook-events \
  -H "Authorization: Bearer rvya_sk_..."

endpoint의 delivery attempts 나열:

GET /api/v1/webhooks/{endpointId}/deliveries
curl https://rivya.ai/api/v1/webhooks/whend_.../deliveries \
  -H "Authorization: Bearer rvya_sk_..."

Delivery records에는 status, HTTP status, attempt number, request id, duration, truncated response snippet, public error fields가 포함됩니다.

Test Event

safe test payload 보내기:

POST /api/v1/webhooks/{endpointId}/test
curl -X POST https://rivya.ai/api/v1/webhooks/whend_.../test \
  -H "Authorization: Bearer rvya_sk_..."

test event는 webhook.test를 사용합니다. generation task를 만들지 않고, credits를 소비하지 않으며, real result URL을 포함하지 않습니다.

Retry Policy

Rivya는 HTTP 2xx를 success로 처리합니다.

Failures에는 network errors, timeout, redirect responses, non-2xx responses가 포함됩니다. Rivya는 최대 five attempts까지 retry합니다.

  • immediately
  • after 1 minute
  • after 5 minutes
  • after 30 minutes
  • after 2 hours

final attempt 후에도 실패하면 event는 failed로 표시됩니다.

Webhook delivery failures는 generation status, credits, refunds, task history를 바꾸지 않습니다.

Security Checklist

  • business logic을 parsing하기 전에 HMAC signature를 검증하세요.
  • stale timestamps를 거부하세요.
  • test events를 generation events와 별도로 다루세요.
  • full signing secrets를 log하지 마세요.
  • receiver가 event를 accept한 뒤에만 2xx를 반환하세요.
  • reconciliation fallback으로 polling을 유지하세요.

관련 페이지

목차