Skip to content
LlamaParse 文档版本 跟随上游 源快照 81a4ed87 更新于 2026-10-16

webhook 让你在 LlamaCloud 任务发生事件时收到实时通知。无需不停地轮询状态,只要配置好 webhook 端点,任务完成、失败或进入其他状态时就会立刻被通知。

概述

LlamaCloud 的 webhook 提供:

  • 实时通知:面向任务
  • 可配置的事件过滤:只接收与你相关的事件
  • 重试逻辑:带指数退避以保证送达
  • 自定义请求头:可用于认证
  • 载荷签名:让你能验证投递确实来自我们且未被篡改

支持的事件

目前 LlamaCloud 支持以下 webhook 事件:

Extract 事件

  • extract.pending - 抽取任务已入队,等待处理
  • extract.success - 抽取任务成功完成
  • extract.error - 抽取任务因错误失败
  • extract.partial_success - 抽取任务完成,但带有若干警告或部分失败
  • extract.cancelled - 抽取任务在完成前被取消

Parse 事件

  • parse.pending - 解析任务已入队,等待处理
  • parse.success - 解析任务成功完成
  • parse.error - 解析任务因错误失败
  • parse.partial_success - 解析任务完成,但带有若干警告或部分失败
  • parse.cancelled - 解析任务在完成前被取消

Classify 事件

  • classify.pending - 分类任务已入队,等待处理
  • classify.running - 分类任务开始处理
  • classify.success - 分类任务成功完成
  • classify.partial_success - 分类任务完成,但带有若干警告或部分失败
  • classify.error - 分类任务因错误失败
  • classify.cancelled - 分类任务在完成前被取消

Split 事件

  • split.pending - 切分任务已入队,等待处理
  • split.processing - 切分任务开始处理
  • split.success - 切分任务成功完成
  • split.error - 切分任务因错误失败
  • split.cancelled - 切分任务在完成前被取消

Verify 事件

  • verify.pending - 校验任务已创建,等待处理
  • verify.running - 校验任务开始处理
  • verify.success - 校验任务完成且结果可取
  • verify.error - 校验任务因错误失败
  • verify.cancelled - 校验任务在完成前被取消

Batch 事件

  • batch.pending - 批次已入队,等待处理
  • batch.running - 批次开始处理其源目录
  • batch.success - 批次结束,逐文件结果可取
  • batch.error - 批次失败,无法给出可靠的逐文件结果集

批次事件描述的是批次整体。batch.success 表示批次已把每个源文件都映射到了一个任务 —— 个别文件仍可能失败,所以要看逐文件结果请读 results(配合 expand=results)。

配置

完整 API schema 在此查阅:https://api.cloud.llamaindex.ai/redoc#tag/LlamaExtract/operation/run_job_api_v1_extraction_jobs_post

基本配置

配置 webhook 的方式是在 API 调用里带上 webhook 配置。请按如下方式传入:

{ ...
  "webhook_configurations": [
    {
      "webhook_url": "string",
      "webhook_signing_secret": "string",
      "webhook_headers": {
        "property1": "string",
        "property2": "string"
      },
      "webhook_events": [
        "extract.pending",
        "extract.success",
        "extract.error"
      ],
      "webhook_output_format": "json"
    }
  ]
  ...
}

本页凡是要上传文件的示例都使用同一份样张:把美国财政部 FY2024 财报的执行摘要下载为 report.pdf:

终端窗口

curl -fL -o report.pdf https://fiscal.treasury.gov/system/files/files/reports-statements/financial-report/2024/executive-summary-2024.pdf

在代码里构造这个数组的方式如下 —— 它作为请求体中的 webhook_configurations 参数传入:

  • Python
  • TypeScript
  • Go
  • Java
  • CLI
webhook_configurations = [
    {
        "webhook_url": "https://your-domain.com/webhook-endpoint",
        "webhook_events": ["extract.success", "extract.error", "parse.success", "parse.error"],
        "webhook_headers": {
            "Authorization": "Bearer your-token",
            "X-Custom-Header": "custom-value"
        },
        "webhook_output_format": "json"
    }
]
const webhookConfigurations = [
  {
    webhook_url: 'https://your-domain.com/webhook-endpoint',
    webhook_events: ['extract.success', 'extract.error', 'parse.success', 'parse.error'],
    webhook_headers: {
      Authorization: 'Bearer your-token',
      'X-Custom-Header': 'custom-value',
    },
    webhook_output_format: 'json',
  },
];
webhookConfigurations := []llamacloud.ParsingNewParamsWebhookConfiguration{
  {
    WebhookURL:    llamacloud.String("https://your-domain.com/webhook-endpoint"),
    WebhookEvents: []string{"extract.success", "extract.error", "parse.success", "parse.error"},
    WebhookHeaders: map[string]any{
      "Authorization":   "Bearer your-token",
      "X-Custom-Header": "custom-value",
    },
    WebhookOutputFormat: "json",
  },
}
ParsingCreateParams.WebhookConfiguration webhookConfiguration =
        ParsingCreateParams.WebhookConfiguration.builder()
                .webhookUrl("https://your-domain.com/webhook-endpoint")
                .addWebhookEvent("extract.success")
                .addWebhookEvent("extract.error")
                .addWebhookEvent("parse.success")
                .addWebhookEvent("parse.error")
                .webhookHeaders(ParsingCreateParams.WebhookConfiguration.WebhookHeaders.builder()
                        .putAdditionalProperty("Authorization", JsonValue.from("Bearer your-token"))
                        .putAdditionalProperty("X-Custom-Header", JsonValue.from("custom-value"))
                        .build())
                .webhookOutputFormat(ParsingCreateParams.WebhookConfiguration.WebhookOutputFormat.JSON)
                .build();

终端窗口

FILE_ID=$(llp files create --file report.pdf --purpose parse | jq -r '.id')

llp parsing create \
  --file-id "$FILE_ID" \
  --tier agentic \
  --version latest \
  --webhook-configuration.webhook-url 'https://your-domain.com/webhook-endpoint' \
  --webhook-configuration.webhook-events '[extract.success, extract.error, parse.success, parse.error]' \
  --webhook-configuration.webhook-headers '{Authorization: "Bearer your-token", X-Custom-Header: custom-value}' \
  --webhook-configuration.webhook-output-format json

本节里 Go、Java 与 CLI 的示例针对的是 Parse 接口。Extract 与 Classify 接受同样的 webhook_configurations JSON 字段,CLI 旗标也完全一致,但 Go 与 Java 会为每个产品各生成一个类型 —— Extract 是 ExtractV2JobCreateWebhookConfigurationParam / ExtractV2JobCreate.WebhookConfiguration,Classify 是 ClassifyCreateRequestWebhookConfigurationParam / ClassifyCreateRequest.WebhookConfiguration。有些成员签名也随名字一起不同(例如这两处 Go 把 webhook_headers 定为 map[string]string、把 webhook_output_format 定为 param.Opt[string],而不是 map[string]any 与 string),所以请读生成的类型,别只换个名字照抄。

复用已保存的配置

每次都内联传 webhook_configurations,意味着每个任务都要重发一遍端点 —— 连同它的签名密钥。更好的做法是每个 project 把端点保存一次,之后按 ID 引用。

向 /api/v1/beta/webhook-configs 发一个 POST 即可保存端点。只有 webhook_url 是必填;webhook_events、webhook_headers、webhook_output_format 与 webhook_signing_secret 都是可选,含义与内联写法完全相同。

终端窗口

curl -X POST 'https://api.cloud.llamaindex.ai/api/v1/beta/webhook-configs?project_id=YOUR_PROJECT_ID' \
  -H "Authorization: Bearer $LLAMA_CLOUD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "webhook_url": "https://your-domain.com/webhook-endpoint",
    "webhook_events": ["parse.success", "parse.error"],
    "webhook_signing_secret": "your-signing-secret",
    "webhook_output_format": "json"
  }'

响应里带着你之后要引用的 ID。签名密钥是只写不读的 —— 永远不会返回,所以响应只告诉你有没有设置过:

{
  "id": "whc-...",
  "tenant_type": "project",
  "tenant_id": "YOUR_PROJECT_ID",
  "webhook_url": "https://your-domain.com/webhook-endpoint",
  "webhook_events": ["parse.success", "parse.error"],
  "webhook_output_format": "json",
  "has_secret": true
}

读取某个已保存的 webhook 时,每个 webhook_headers 的取值、以及 webhook_url 里的 user:password 都会被掩码为 **********。更新请求里的 Headers 会整张表替换,而传入掩码值则保留已存的那个。改动目的地(scheme、host、port 或 path)会清除已存的 Headers,除非更新时把它们的真实值一并重发;只改 URL 里的 user:password 则不会清除。

随后在创建任务时用 webhook_configuration_ids 引用它。SDK 里 webhook_configs.create(TypeScript 中是 webhookConfigs.create)会保存端点并返回同一个 id:

  • Python
  • TypeScript
  • API
from llama_cloud import LlamaCloud

client = LlamaCloud()  # reads LLAMA_CLOUD_API_KEY from the environment

# Save the endpoint once and keep its id; later jobs only pass webhook_configuration_ids
webhook_config = client.webhook_configs.create(
    webhook_url="https://your-domain.com/webhook-endpoint",
    webhook_events=["parse.success", "parse.error"],
    webhook_signing_secret="your-signing-secret",
    webhook_output_format="json",
)
file = client.files.create(file="./report.pdf", purpose="parse")

job = client.parsing.create(
    file_id=file.id,
    tier="agentic",
    version="latest",
    webhook_configuration_ids=[webhook_config.id],
)
import LlamaCloud from '@llamaindex/llama-cloud';
import fs from 'fs';

const client = new LlamaCloud(); // reads LLAMA_CLOUD_API_KEY from the environment

// Save the endpoint once and keep its id; later jobs only pass webhook_configuration_ids
const webhookConfig = await client.webhookConfigs.create({
  webhook_url: 'https://your-domain.com/webhook-endpoint',
  webhook_events: ['parse.success', 'parse.error'],
  webhook_signing_secret: 'your-signing-secret',
  webhook_output_format: 'json',
});
const file = await client.files.create({
  file: fs.createReadStream('./report.pdf'),
  purpose: 'parse',
});

const job = await client.parsing.create({
  file_id: file.id,
  tier: 'agentic',
  version: 'latest',
  webhook_configuration_ids: [webhookConfig.id],
});

终端窗口

curl -X POST 'https://api.cloud.llamaindex.ai/api/v2/parse?project_id=YOUR_PROJECT_ID' \
  -H "Authorization: Bearer $LLAMA_CLOUD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "file_id": "'"$FILE_ID"'",
    "tier": "agentic",
    "version": "latest",
    "webhook_configuration_ids": ["whc-..."]
  }'

已保存的端点在服务端解析,并追加在同一请求里任何内联 webhook_configurations 之后,所以两种方式可以混用 —— 一个用于常规告警的已存端点,加上某个单独任务的一次性内联端点。

由于密钥始终留在服务端,该端点的签名方式与你内联传入密钥时完全一致。如何验证一次投递,见用签名保护 webhook。

有两项限制:

  • 单次创建请求最多引用 4 个已保存的配置 ID。
  • 解析出的端点加上任何内联 webhook_configurations,每个任务合计最多 10 个。

若某个配置 ID 不存在于该任务所属的 project,会被 400 拒绝。ID 的作用域限于其 project,别的项目里的 ID 解析不出来。

可用性

webhook_configuration_ids 在 Parse、Extract、Classify、Split 与 Verify 的任务创建上都被接受。

已保存的配置(/api/v1/beta/webhook-configs)可以在所有 SDK 与 CLI 上创建、列举、更新与删除:Python 里是 webhook_configs,TypeScript 与 Java 里是 webhookConfigs,Go 里是 WebhookConfigs,CLI 里是 llp webhook-configs。

事件过滤

可以通过 webhook_events 数组指定要接收哪些事件。不指定时,所有事件都会发送。

  • Python
  • TypeScript
  • Go
  • Java
  • CLI
# Receive only success and error events
webhook_configurations = [
    {
        "webhook_url": "https://your-domain.com/webhook",
        "webhook_events": ["extract.success", "extract.error", "parse.success", "parse.error"],
        "webhook_output_format": "json"
    }
]

# Receive all events (default behavior)
webhook_configurations = [
    {
        "webhook_url": "https://your-domain.com/webhook",
        "webhook_output_format": "json"
        # webhook_events omitted = receive all events
    }
]
// Receive only success and error events
const webhookConfigurations = [
  {
    webhook_url: 'https://your-domain.com/webhook',
    webhook_events: ['extract.success', 'extract.error', 'parse.success', 'parse.error'],
    webhook_output_format: 'json',
  },
];

// Receive all events (default behavior)
const allEventConfigurations = [
  {
    webhook_url: 'https://your-domain.com/webhook',
    webhook_output_format: 'json',
    // webhook_events omitted = receive all events
  },
];
// Receive only success and error events
webhookConfigurations := []llamacloud.ParsingNewParamsWebhookConfiguration{
  {
    WebhookURL:          llamacloud.String("https://your-domain.com/webhook"),
    WebhookEvents:       []string{"extract.success", "extract.error", "parse.success", "parse.error"},
    WebhookOutputFormat: "json",
  },
}

// Receive all events (default behavior)
allEventConfigurations := []llamacloud.ParsingNewParamsWebhookConfiguration{
  {
    WebhookURL:          llamacloud.String("https://your-domain.com/webhook"),
    WebhookOutputFormat: "json",
    // WebhookEvents omitted = receive all events
  },
}
// Receive only success and error events
ParsingCreateParams.WebhookConfiguration webhookConfiguration =
        ParsingCreateParams.WebhookConfiguration.builder()
                .webhookUrl("https://your-domain.com/webhook")
                .addWebhookEvent("extract.success")
                .addWebhookEvent("extract.error")
                .addWebhookEvent("parse.success")
                .addWebhookEvent("parse.error")
                .webhookOutputFormat(ParsingCreateParams.WebhookConfiguration.WebhookOutputFormat.JSON)
                .build();

// Receive all events (default behavior)
ParsingCreateParams.WebhookConfiguration allEventConfiguration =
        ParsingCreateParams.WebhookConfiguration.builder()
                .webhookUrl("https://your-domain.com/webhook")
                .webhookOutputFormat(ParsingCreateParams.WebhookConfiguration.WebhookOutputFormat.JSON)
                // webhookEvents omitted = receive all events
                .build();

终端窗口

FILE_ID=$(llp files create --file report.pdf --purpose parse | jq -r '.id')

# Receive only success and error events
llp parsing create \
  --file-id "$FILE_ID" \
  --tier agentic \
  --version latest \
  --webhook-configuration.webhook-url 'https://your-domain.com/webhook' \
  --webhook-configuration.webhook-events '[extract.success, extract.error, parse.success, parse.error]' \
  --webhook-configuration.webhook-output-format json

# Receive all events (default behavior)
llp parsing create \
  --file-id "$FILE_ID" \
  --tier agentic \
  --version latest \
  --webhook-configuration.webhook-url 'https://your-domain.com/webhook' \
  --webhook-configuration.webhook-output-format json

自定义请求头

加上自定义请求头,用于认证或其他用途:

  • Python
  • TypeScript
  • Go
  • Java
  • CLI
webhook_configurations = [
    {
        "webhook_url": "https://your-domain.com/webhook",
        "webhook_headers": {
            "Authorization": "Bearer your-secret-token",
            "X-Source": "llamacloud",
            "Content-Type": "application/json"  # This is set automatically
        },
        "webhook_output_format": "json"
    }
]
const webhookConfigurations = [
  {
    webhook_url: 'https://your-domain.com/webhook',
    webhook_headers: {
      Authorization: 'Bearer your-secret-token',
      'X-Source': 'llamacloud',
      'Content-Type': 'application/json', // This is set automatically
    },
    webhook_output_format: 'json',
  },
];
webhookConfigurations := []llamacloud.ParsingNewParamsWebhookConfiguration{
  {
    WebhookURL: llamacloud.String("https://your-domain.com/webhook"),
    WebhookHeaders: map[string]any{
      "Authorization": "Bearer your-secret-token",
      "X-Source":      "llamacloud",
      "Content-Type":  "application/json", // This is set automatically
    },
    WebhookOutputFormat: "json",
  },
}
ParsingCreateParams.WebhookConfiguration webhookConfiguration =
        ParsingCreateParams.WebhookConfiguration.builder()
                .webhookUrl("https://your-domain.com/webhook")
                .webhookHeaders(ParsingCreateParams.WebhookConfiguration.WebhookHeaders.builder()
                        .putAdditionalProperty("Authorization", JsonValue.from("Bearer your-secret-token"))
                        .putAdditionalProperty("X-Source", JsonValue.from("llamacloud"))
                        // Content-Type is set automatically
                        .putAdditionalProperty("Content-Type", JsonValue.from("application/json"))
                        .build())
                .webhookOutputFormat(ParsingCreateParams.WebhookConfiguration.WebhookOutputFormat.JSON)
                .build();

终端窗口

FILE_ID=$(llp files create --file report.pdf --purpose parse | jq -r '.id')

# Content-Type is set automatically
llp parsing create \
  --file-id "$FILE_ID" \
  --tier agentic \
  --version latest \
  --webhook-configuration.webhook-url 'https://your-domain.com/webhook' \
  --webhook-configuration.webhook-headers '{Authorization: "Bearer your-secret-token", X-Source: llamacloud, Content-Type: application/json}' \
  --webhook-configuration.webhook-output-format json

webhook 载荷

事件发生时,LlamaCloud 会向你的 webhook URL 发一个 POST 请求,载荷结构如下:

{
    "event_id": "149744dd-9002-4411-a6c7-9635da372caa",
    "event_type": "parse.success",
    "timestamp": 1753985275.1154444,
    "data": {
        "id": "a9a57884-921e-4ec2-b555-f4e5a97ec02a",
        "job_id": "a9a57884-921e-4ec2-b555-f4e5a97ec02a"
    }
}

载荷字段

  • event_id:本次 webhook 事件的唯一标识
  • event_type:发生的事件类型(例如 “extract.success”、“parse.success”)
  • timestamp:事件发生时的 Unix 时间戳
  • data:事件相关的数据,含任务详情与结果

HTTP 请求头

LlamaCloud 在 webhook 请求里带上这些头:

  • Content-Type: application/json
  • User-Agent: llamaindex-webhook-service/1.0
  • X-Webhook-Event-ID: {event_id}
  • X-Webhook-Event-Type: {event_type}
  • LC-Signature: sha256="signature" —— 仅在配置了 webhook_signing_secret 时出现(见保护 webhook)
  • 以及你配置的任何自定义请求头

用签名保护 webhook

要验证一次 webhook 投递确实来自 LlamaCloud、且传输途中未被篡改,请为该端点配置 webhook_signing_secret。设置了签名密钥后,每个请求都会被签名,签名放在 LC-Signature 头里。

签名是以你的签名密钥为 key、对原始请求体做的 HMAC-SHA256 十六进制摘要,并带一个 sha256= 前缀。sha256= 前缀标明的是算法,因此将来换算法时无需改动头名。

  • Python
  • TypeScript
  • Go
  • Java
  • CLI
webhook_configurations = [
    {
        "webhook_url": "https://your-domain.com/webhook",
        "webhook_signing_secret": "your-shared-secret",
        "webhook_events": ["parse.success", "parse.error"],
        "webhook_output_format": "json",
    }
]
const webhookConfigurations = [
  {
    webhook_url: 'https://your-domain.com/webhook',
    webhook_signing_secret: 'your-shared-secret',
    webhook_events: ['parse.success', 'parse.error'],
    webhook_output_format: 'json',
  },
];
webhookConfigurations := []llamacloud.ParsingNewParamsWebhookConfiguration{
  {
    WebhookURL:           llamacloud.String("https://your-domain.com/webhook"),
    WebhookSigningSecret: llamacloud.String("your-shared-secret"),
    WebhookEvents:        []string{"parse.success", "parse.error"},
    WebhookOutputFormat:  "json",
  },
}
ParsingCreateParams.WebhookConfiguration webhookConfiguration =
        ParsingCreateParams.WebhookConfiguration.builder()
                .webhookUrl("https://your-domain.com/webhook")
                // The Java SDK has no typed setter for the signing secret yet
                .putAdditionalProperty("webhook_signing_secret", JsonValue.from("your-shared-secret"))
                .addWebhookEvent("parse.success")
                .addWebhookEvent("parse.error")
                .webhookOutputFormat(ParsingCreateParams.WebhookConfiguration.WebhookOutputFormat.JSON)
                .build();

终端窗口

FILE_ID=$(llp files create --file report.pdf --purpose parse | jq -r '.id')

llp parsing create \
  --file-id "$FILE_ID" \
  --tier agentic \
  --version latest \
  --webhook-configuration.webhook-url 'https://your-domain.com/webhook' \
  --webhook-configuration.webhook-signing-secret 'your-shared-secret' \
  --webhook-configuration.webhook-events '[parse.success, parse.error]' \
  --webhook-configuration.webhook-output-format json

验证一次投递

用你的密钥对原始请求体重新计算 HMAC,并与头的取值做恒定时间比较。签名一定要在请求体的原始字节上计算 —— 不要先把解析过的 JSON 重新序列化,那会改变字节从而让校验失败。

  • Python
  • TypeScript
  • Go
  • Java
import hashlib
import hmac

def is_valid_signature(secret: str, body: bytes, signature_header: str) -> bool:
    """Validate the LC-Signature header against the raw request body."""
    expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    # Constant-time comparison avoids leaking the digest via timing.
    return hmac.compare_digest(expected, signature_header)

例如一个 FastAPI 接收端:

from fastapi import FastAPI, Header, HTTPException, Request

app = FastAPI()
WEBHOOK_SECRET = "your-shared-secret"

@app.post("/webhook")
async def receive_webhook(
    request: Request,
    lc_signature: str = Header(default=""),
) -> dict:
    body = await request.body()  # raw bytes, before JSON parsing
    if not is_valid_signature(WEBHOOK_SECRET, body, lc_signature):
        raise HTTPException(status_code=401, detail="Invalid signature")
    # signature verified — safe to process
    return {"ok": True}
import crypto from 'node:crypto';

/** Validate the LC-Signature header against the raw request body. */
export function isValidSignature(secret: string, body: Buffer, signatureHeader: string): boolean {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(body).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader);
  // Constant-time comparison avoids leaking the digest via timing.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

例如一个 Express 接收端:

import express from 'express';

const app = express();
const WEBHOOK_SECRET = 'your-shared-secret';

// express.raw() keeps the raw bytes; express.json() would discard them
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.header('LC-Signature') ?? '';
  if (!isValidSignature(WEBHOOK_SECRET, req.body, signature)) {
    return res.status(401).json({ detail: 'Invalid signature' });
  }
  // signature verified — safe to process
  res.json({ ok: true });
});

app.listen(8000);

辅助函数与一个 net/http 接收端:

package main

import (
  "crypto/hmac"
  "crypto/sha256"
  "encoding/hex"
  "io"
  "log"
  "net/http"
)

const webhookSecret = "your-shared-secret"

// isValidSignature validates the LC-Signature header against the raw request body.
func isValidSignature(secret string, body []byte, signatureHeader string) bool {
  mac := hmac.New(sha256.New, []byte(secret))
  mac.Write(body)
  expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
  // Constant-time comparison avoids leaking the digest via timing.
  return hmac.Equal([]byte(expected), []byte(signatureHeader))
}

func main() {
  http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
    body, err := io.ReadAll(r.Body) // raw bytes, before JSON parsing
    if err != nil {
      http.Error(w, "unreadable body", http.StatusBadRequest)
      return
    }
    if !isValidSignature(webhookSecret, body, r.Header.Get("LC-Signature")) {
      http.Error(w, "Invalid signature", http.StatusUnauthorized)
      return
    }
    // signature verified — safe to process
    w.Header().Set("Content-Type", "application/json")
    w.Write([]byte(`{"ok": true}`))
  })

  log.Fatal(http.ListenAndServe(":8000", nil))
}

辅助函数与一个基于 JDK HttpServer 的接收端:

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class WebhookReceiver {

    private static final String WEBHOOK_SECRET = "your-shared-secret";

    /** Validate the LC-Signature header against the raw request body. */
    static boolean isValidSignature(String secret, byte[] body, String signatureHeader) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        StringBuilder expected = new StringBuilder("sha256=");
        for (byte b : mac.doFinal(body)) {
            expected.append(String.format("%02x", b));
        }
        // Constant-time comparison avoids leaking the digest via timing.
        return MessageDigest.isEqual(
                expected.toString().getBytes(StandardCharsets.UTF_8),
                signatureHeader.getBytes(StandardCharsets.UTF_8));
    }

    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
        server.createContext("/webhook", exchange -> {
            byte[] body = exchange.getRequestBody().readAllBytes(); // raw bytes, before JSON parsing
            String signature = exchange.getRequestHeaders().getFirst("LC-Signature");
            boolean valid;
            try {
                valid = signature != null && isValidSignature(WEBHOOK_SECRET, body, signature);
            } catch (Exception e) {
                valid = false;
            }
            if (!valid) {
                respond(exchange, 401, "{\"detail\": \"Invalid signature\"}");
                return;
            }
            // signature verified — safe to process
            respond(exchange, 200, "{\"ok\": true}");
        });
        server.start();
    }

    static void respond(HttpExchange exchange, int status, String json) throws IOException {
        byte[] payload = json.getBytes(StandardCharsets.UTF_8);
        exchange.getResponseHeaders().set("Content-Type", "application/json");
        exchange.sendResponseHeaders(status, payload.length);
        try (OutputStream out = exchange.getResponseBody()) {
            out.write(payload);
        }
    }
}

请把密钥当作其他任何凭据一样对待:安全存储、绝不提交进版本库,若有泄露风险就轮换。

出口 IP 地址

LlamaCloud 用固定、按地域区分的出口 IP 投递 webhook。如果你的 webhook 端点在防火墙之后、或有网络准入规则,请把账号所在地域对应的 IP 加进白名单,投递才会被放行:

地域IP 地址
美国 / 北美52.5.178.213
EU18.196.227.29

少数情况下这些地址会变动,所以不要把它当作唯一的校验手段。要确认请求确实来自 LlamaCloud,请配置 webhook_signing_secret 并校验每个入站 webhook 的签名(和/或校验自定义头,例如 Authorization 的 bearer token)。

重试行为

只要你的端点返回 200–299 区间内的任意 HTTP 状态,这次投递就算成功。否则:

限额取值
投递尝试次数3 次;第二次前等 1 秒、第三次前等 2 秒
单次投递超时最多 30 秒,某些产品更短。请先立刻返回 2xx,把工作放到后面做
每个任务的端点合计 10 个;每个请求最多 4 个已保存的配置 ID
目的地必须公网可达的 HTTP 或 HTTPS URL;私有与回环地址会被拦截

示例:用 Inngest 接收 webhook

你可以把 webhook_url 指向一个 Inngest webhook,无需自己起服务器就能收到 LlamaCloud 的事件。

在 Inngest 控制台里创建一个 webhook,并加上这段 Transform Function,让每个事件都按它的 event_type 命名:

function transform(evt) {
  return { name: `Llamacloud/${evt.event_type}`, data: evt };
}

随后把 Inngest 的 URL 作为你的 webhook_url:

webhook_configurations = [
    {
        "webhook_url": "https://inn.gs/e/<your-inngest-key>",
        "webhook_events": ["parse.success", "parse.error"],
        "webhook_output_format": "json"
    }
]

让一个 Inngest 函数订阅转换后的事件,并用载荷里的 job_id 取回解析结果:

inngest.createFunction(
  { id: "llamacloud-parse-success" },
  { event: "Llamacloud/parse.success" },
  async ({ event }) => {
    const jobId = event.data.data.job_id;
    const res = await fetch(
      `https://api.cloud.llamaindex.ai/api/v2/parse/${jobId}?expand=markdown`,
      { headers: { Authorization: `Bearer ${process.env.LLAMA_CLOUD_API_KEY}` } },
    );
    const { markdown } = await res.json();
    // do something with the parsed markdown
  },
);

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。