Webhooks Inbound
Guide to receive events from external systems in Sarah
Last updated: 2025-01-26
The /api/integration/in endpoint allows external systems (such as Make or Zapier) to send events to Sarah to create or update domain entities or notify status changes.
Endpoint
POST /api/integration/in
GET /api/integration/in
Authentication
All requests must include a Bearer authentication header:
Authorization: Bearer <integration_key>
The integration_key must match a company record in Sarah. You can obtain your Integration Key from Settings > Integrations.
POST /api/integration/in
Sends an event to Sarah using a consistent envelope.
Recommended Headers
httpAuthorization: Bearer <integration_key>
Content-Type: application/json
x-idempotency-key: <unique-string>
x-sent-at: 2025-01-26T12:34:56.789Z
x-signature: <optional hmac hex>
Envelope Structure
All events must follow this structure:
json{
"event": "product.upserted",
"data": {
/* event-specific payload object */
}
}
Supported Events
| Event | Description | data Structure |
|---|---|---|
sales.created | New sale created | { "sale": { ... } } |
sales.paid | Sale marked as paid | { "sale": { "id": 123 } } |
sales.cancelled | Sale cancelled | { "sale": { "id": 123 } } |
invoice.issued | Fiscal receipt issued | { "invoice": { ... } } |
movement.created | Inventory movement created | { "movement": { ... } } |
product.upserted | Product created or updated | { "product": { ... } } |
product.stock_updated | Product stock updated | { "product": { "id": 123, "stock": 50 } } |
Example: Update Product
httpPOST /api/integration/in
Authorization: Bearer sk_live_abc123...
Content-Type: application/json
x-idempotency-key: product.upserted:123
x-sent-at: 2025-01-26T12:34:56.789Z
{
"event": "product.upserted",
"data": {
"product": {
"id": 123,
"sku": "ABC-001",
"name": "Example Product",
"stock": 40,
"base_price": 1500.00
}
}
}
Responses
Success (200):
json{
"status": "ok"
}
Success with idempotency (200):
json{
"status": "ok",
"idempotent": true
}
Validation error (4xx):
json{
"error": "Invalid or missing event"
}
Security
HMAC Signature (Optional but Recommended)
For enhanced security, you can include an HMAC signature:
- Serialize the JSON exactly (without extra spaces)
- Calculate HMAC SHA-256 with your
integration_keyas secret - Convert to lowercase hexadecimal string
- Send in header:
x-signature: <hex>
If the signature is present but doesn't match, the request will be rejected with 401.
Replay Protection
The x-sent-at header must contain an ISO timestamp. Sarah will reject events outside a ±5 minutes window.
Idempotency
The x-idempotency-key header ensures an event is only processed once. If you send the same event with the same key, you'll receive { "idempotent": true } without side effects.
GET /api/integration/in
Query stored events for your company (polling).
Headers
Authorization: Bearer <integration_key>
Query Parameters
| Parameter | Description | Default |
|---|---|---|
limit | Maximum events to return | 50 (max 200) |
since | ISO timestamp; returns events with received_at > since | null |
Example
httpGET /api/integration/in?limit=10&since=2025-01-26T10:00:00Z
Authorization: Bearer sk_live_abc123...
Response
json{
"events": [
{
"id": "uuid",
"received_at": "2025-01-26T12:35:15.000Z",
"event": "product.upserted",
"idempotency_key": "product.upserted:123",
"payload": {
"event": "product.upserted",
"data": { ... }
},
"signature_valid": true
}
],
"meta": {
"limit": 50,
"since": null
}
}
Status Codes
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Invalid JSON or x-sent-at outside window |
| 401 | Missing, invalid Bearer, or incorrect HMAC signature |
| 422 | Invalid event or incorrect data structure |
| 500 | Internal server error |
Best Practices
- Always use idempotency keys when you can retry (e.g., network uncertainty)
- Provide
x-sent-atandx-signaturefor enhanced security - Use incremental polling via
GET /api/integration/in?since=<last_received_at>if webhooks are not feasible - Log the
idempotentflag to suppress duplicate downstream processing - Handle 4xx errors appropriately and don't retry indefinitely