主题
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 jsonwebhook 载荷
事件发生时,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/jsonUser-Agent: llamaindex-webhook-service/1.0X-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 |
| EU | 18.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
},
);