# Responses and errors

Every status code and error message an AbabilX flock webhook can return, what each one means, and whether to retry.

Every answer from the server is JSON with a success field. On success it also has data or duplicate; on failure it has error, a short English string that is stable enough to match on. The one exception is 404, which is plain text.

## Success

| Status | Body | Meaning |
| --- | --- | --- |
| 201 Created | {"success":true,"data":{"id":"<uuid>","created_at":"<RFC 3339>"}} | The message is stored and on its way to members. |
| 200 OK | {"success":true,"duplicate":true} | This event_id was already posted to this flock in the last 7 days. Nothing new was stored; treat it as success. |

## Every error

| Status | error | Cause | Retry? |
| --- | --- | --- | --- |
| 400 | invalid request body | Body is not one JSON object; a field is not a string; JSON over 64 KB; multipart over 351 MB or malformed; payload_json missing, empty, repeated or not JSON; an HTML form post. | No. Fix the request. |
| 400 | content or file is required | No message, no content and no files (a Slack-style {"text":…} body lands here). | No. |
| 400 | content exceeds 6000 characters | Text over 6,000 bytes of UTF-8. | No. Shorten or split it. |
| 400 | content exceeds 1000 words | Text over 1,000 words. | No. |
| 400 | event_id too long | event_id over 200 bytes. | No. |
| 400 | max 10 attachments allowed | More than 10 files. | No. |
| 400 | invalid file size | An empty file, or one over 350 MB. | No. |
| 400 | invalid file name | A file part with no usable name, or a name over 255 bytes. | No. |
| 400 | attachments exceed 350 MB total limit | Files add up to more than 350 MB. | No. |
| 401 | unauthorized | Malformed, unknown or replaced URL; wrong secret; the flock was deleted. | No. Get the current URL from the flock's owner. |
| 404 | 404 page not found (plain text) | Wrong path, or a method other than POST. | No. |
| 429 | rate_limit_exceeded | Over 60 messages a minute for this flock, or 120 requests a minute from your IP. | Yes, after Retry-After seconds. |
| 429 | too many requests, slow down | Over the API-wide limit for your IP. | Yes, after Retry-After (1 second). |
| 500 | failed to store attachment | A file could not be saved. Nothing was posted. | Yes, with backoff. |
| 500 | (other text) | A server fault. Nothing was posted. | Yes, with backoff and the same event_id. |
| 502 / 504 | (proxy page) | The host could not reach the server in time. | Yes, with backoff and the same event_id. |
| 503 | file uploads are not configured | File storage is down. Text-only posts still work. | Later, or send without files. |

> Checks run in this order: URL shape → IP rate limit → URL lookup → secret → flock rate limit → body. So a wrong URL can still answer 429 if it is hammered, and a bad body is only reported for a valid URL.

## Example of every response

Exact bodies as the server sends them. Match on the status first, then on error.

```json
{
  "success": true,
  "data": {
    "id": "6f1c2a9e-5b7d-4c1e-9a43-0d2f8b7e1c55",
    "created_at": "2026-10-19T09:30:00.123456Z"
  }
}
```

```json
{
  "success": true,
  "duplicate": true
}
```

```json
{"success":false,"error":"invalid request body"}
```

```json
{"success":false,"error":"content or file is required"}
```

```json
{"success":false,"error":"content exceeds 6000 characters"}
```

```json
{"success":false,"error":"content exceeds 1000 words"}
```

```json
{"success":false,"error":"event_id too long"}
```

```json
{"success":false,"error":"max 10 attachments allowed"}
```

```json
{"success":false,"error":"invalid file size"}
```

```json
{"success":false,"error":"invalid file name"}
```

```json
{"success":false,"error":"attachments exceed 350 MB total limit"}
```

```json
{"success":false,"error":"unauthorized"}
```

```text
404 page not found
```

```http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json; charset=utf-8
Retry-After: 23

{"success":false,"error":"rate_limit_exceeded"}
```

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 1

{"success":false,"error":"too many requests, slow down"}
```

```json
{"success":false,"error":"failed to store attachment"}
```

```json
{"success":false,"error":"file uploads are not configured"}
```

## What to do with an answer

1. **POST with an event_id** (your app): Make the id unique per real event, such as a run number or an order id.
- _200 or 201_
   1. **Done** (your app): Both mean the message is in the flock.
- _429_
   1. **Wait Retry-After seconds** (your app): Then send the same request again.
- _500, 502, 503, 504, timeout_
   1. **Back off and resend** (your app): 1 s, 2 s, 4 s … up to about 5 tries, same event_id, so a retry can never post twice.
- _400, 401, 404_
   1. **Stop and alert a person** (your app): Resending will fail the same way.

## Response headers

| Header | When | Value |
| --- | --- | --- |
| Content-Type | Always | application/json; charset=utf-8 (text/plain for 404) |
| Retry-After | 429 | Whole seconds to wait |
| Access-Control-Allow-Origin | When the request has an Origin | * |
| Access-Control-Expose-Headers | When the request has an Origin | Retry-After |

## Browsers and CORS

Any website may call a flock URL. A JSON POST from a browser first sends an OPTIONS preflight, which answers 204 with Access-Control-Allow-Origin: *, Access-Control-Allow-Methods: POST, OPTIONS and any requested headers. No cookies are sent or needed, so do not set credentials: "include".

```http
OPTIONS /hooks/chat/<flock-id>/<secret>
Origin: https://shop.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST,OPTIONS
Access-Control-Allow-Headers: *,Content-Type,X-Webhook-Event-Id
Access-Control-Max-Age: 43200
```

---
Source: https://chat.ababilx.com/docs/webhook-responses
Last updated: 2026-10-19
