Step 3: Send a Webhook
Send your first webhook payloads to Webbu for buffering.
Point your webhook sender at the buffer's ingest URL:
POST https://webbu.dev/api/v1/ingest/{projectId}/{bufferId}The body must be JSON, sent with Content-Type: application/json.
Send a test webhook
curl -X POST https://webbu.dev/api/v1/ingest/p_shop/b_orders \
-H "Content-Type: application/json" \
-d '{
"event": "order.created",
"customerId": "cus_001",
"orderId": "ord_100",
"amount": 49.99
}'const res = await fetch('https://webbu.dev/api/v1/ingest/p_shop/b_orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event: 'order.created',
customerId: 'cus_001',
orderId: 'ord_100',
amount: 49.99,
}),
});
console.log(res.status, await res.json()); // 202 { status: 'accepted', ... }resp = requests.post(
"https://webbu.dev/api/v1/ingest/p_shop/b_orders",
json={
"event": "order.created",
"customerId": "cus_001",
"orderId": "ord_100",
"amount": 49.99,
},
)
print(resp.status_code, resp.json()) # 202 {'status': 'accepted', ...}Response (202 Accepted):
{
"status": "accepted",
"messageId": "5b0c6f0e-5f7e-4a51-9d0b-0d6c2a9f1e11",
"itemId": "0e6d1c2b-3a4f-4b5c-8d7e-9f0a1b2c3d4e"
}202 means the payload is stored and will be delivered when a flush condition is met. Each accepted webhook uses 1 credit per started KB of payload (see Credits & Billing).
The ingest endpoint doesn't require an API key, so treat the ingest URL as a secret. You can send X-API-Key (it must then be valid for this buffer's project), and you can turn on source verification so only signed requests are accepted.
Send more webhooks
Send a few more with the same customerId to see batching:
curl -X POST https://webbu.dev/api/v1/ingest/p_shop/b_orders \
-H "Content-Type: application/json" \
-d '{"event": "order.paid", "customerId": "cus_001", "orderId": "ord_100"}'
curl -X POST https://webbu.dev/api/v1/ingest/p_shop/b_orders \
-H "Content-Type: application/json" \
-d '{"event": "order.shipped", "customerId": "cus_001", "trackingId": "TRK123"}'for payload in [
{"event": "order.paid", "customerId": "cus_001", "orderId": "ord_100"},
{"event": "order.shipped", "customerId": "cus_001", "trackingId": "TRK123"},
]:
requests.post("https://webbu.dev/api/v1/ingest/p_shop/b_orders", json=payload)All three share customerId: "cus_001", so they land in the same group and are delivered together.
Deduplication
Give each event a stable message ID so a resend isn't stored twice. Webbu uses, in order:
- a top-level
messageIdfield in the JSON body - the
X-Idempotency-Keyrequest header - otherwise a random UUID (no deduplication)
curl -X POST https://webbu.dev/api/v1/ingest/p_shop/b_orders \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: evt_unique_123" \
-d '{"event": "order.created", "customerId": "cus_001"}'A duplicate gets 202 with "duplicate": true and uses no credits. Duplicates are detected while the original is still buffered in the same group; after the batch has been delivered, the same ID is accepted again.
Common errors
| Status | Body error | Fix |
|---|---|---|
400 | Could not extract group key from request | The body has no value at groupKey.path and the fallback header is missing |
400 | Buffer is disabled | Re-enable the buffer (it may have been auto-paused) |
400 | Buffer does not belong to this project | Use the buffer's own projectId in the URL |
400 | (HTML "Bad Request") | The body isn't valid JSON |
401 | Missing signature header / Invalid signature | Source verification is on; sign the request correctly |
401 | Unauthorized | You sent an X-API-Key that is invalid or revoked |
403 | API key does not have access to this buffer | The key belongs to another account or isn't scoped to this project |
404 | Buffer not found | Check the bufferId in the URL |
429 | Insufficient credits | Buy credits or wait for your monthly reset; see Credits & Billing |