Emit an event
Fan a custom event out to every webhook subscriber listening for it.
POST /v1/events/emit · requires events:emit
Body parameters
Prop
Type
Returns
Prop
Type
Each entry of results contains subscriptionId, name, status (delivered or failed) and httpStatus. httpStatus is the status the receiver responded with, null when nothing was sent because the URL is not allowed, and omitted when the request failed before any response, such as a timeout. A failure adds error.
Example
curl -X POST https://ceejay.communitystudios.xyz/api/v1/events/emit \
-H "Authorization: Bearer $CEEJAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"event": "purchase.completed",
"data": {
"discordId": "409876543210987654",
"sku": "season-pass",
"value": 24.99
},
"idempotencyKey": "order-88213-event"
}'The TypeScript tab calls ceejay.events.emit from the TypeScript SDK. It returns the data of the response below.
{
"success": true,
"data": {
"event": "custom.purchase.completed",
"subscribersNotified": 2,
"results": [
{
"subscriptionId": 4,
"name": "Ops relay",
"status": "delivered",
"httpStatus": 200
},
{
"subscriptionId": 7,
"name": "Data warehouse",
"status": "failed",
"httpStatus": 503,
"error": "HTTP 503"
}
],
"duplicate": false
}
}Event names
A name is made of letters from A to Z in either case, digits, and the four characters ., _, : and -, such as purchase.completed or order:shipped_v2. It can be 1 to 93 characters long. You may write custom. in front of it yourself: those seven characters are not counted, and the event is delivered under the same name either way.
Spaces, accented letters, emoji and every other character are refused. The name is sent in the X-CEEJAY-Event header as well as in the body. A header cannot carry a line break, a control character or a character above U+00FF, and not every receiver reads the rest the same way, so a name is limited to characters that arrive unchanged everywhere.
Who receives it
Webhooks that are enabled and listen for custom.event (the dialog calls it An event sent through the API), for this exact name (custom.purchase.completed), or for * (Every event). The call returns 200 with subscribersNotified: 0 when none do.
Each webhook is called in turn, with a 5-second timeout, and the call returns once every one has been attempted, so its time grows with the number of webhooks. The delivery is signed and sent like any other event, as attempt 1. One call creates one delivery ID: every webhook receives the same body and the same X-CEEJAY-Delivery. A failed delivery counts toward the webhook's failure counter and is retried by CEEJAY, up to 4 more times. Webhooks has the signing, retry and disabling rules.
Your data is delivered as you sent it. CEEJAY adds nothing to it.
Retrying safely
Send an idempotencyKey with every call you might have to retry, such as after a timeout. It is a string of 1 to 128 characters that you choose, sent in the JSON body: an Idempotency-Key header is not read.
- A repeat changes nothing. When the same request is sent again with its key, CEEJAY returns the response it returned the first time, with
duplicate: trueand the headerIdempotent-Replayed: true. - One key is one request. Here the same request is the same event name and data. Two requests for one event need two keys, for example
match-9:coinsandmatch-9:xp. A key that was used for a different request, here or on any other request of the API, returns409with the codeidempotency_key_reused, and nothing is changed. - A rejected request does not use up its key. A call that was rejected can be sent again with the same key once the cause is fixed.
- A key belongs to the API key that sent it. Another API key of your community can send the same text, and it is a different key.
- Keys are retained for 30 days. Use something you can rebuild after a crash, such as an order ID or a match ID.
- On an instance that does not support idempotency keys yet, a call that sends one returns
503with the codeidempotency_unavailable, and nothing is changed. Community details reports whether keys are stored, underfeatures.retryKeys. A body that sendsidempotency_keygets no key here: this request readsidempotencyKeyonly. - Without a key, a repeated call is a new call.
A webhook cannot be recalled once sent, so CEEJAY never sends one key twice:
- A call that returned
200is stored against the key, also when some or all of its deliveries failed. CEEJAY retries those itself, so a repeat with the same key sends nothing. To send the event again, use a new key. - When a first request was started more than a minute ago and never finished, the same key returns
409with the codeidempotency_outcome_unknown.
Without a key, sending the same request twice sends the event twice.
Errors
| Status | code | error | When |
|---|---|---|---|
400 | bad_request | The request body must be a JSON object | The body is null, text, a number or a list. A request with no Content-Type header is read as text, so it gets this response too |
400 | bad_request | event is required and must be a string | event is missing, empty or not text |
400 | bad_request | event name cannot exceed 100 characters | event is longer than 100 characters |
400 | bad_request | event name must be 1-93 letters, digits, ".", "_", ":" or "-" (not counting a leading "custom.") | The name contains a character that is not allowed, is custom. with nothing after it, or is longer than 93 characters after the prefix |
400 | bad_request | data cannot contain a null character | data contains a null character (U+0000) somewhere, in a value or in a field name |
400 | bad_request | data is nested too deeply | data nests lists and objects more than 32 levels deep |
400 | bad_request | data must be valid Unicode text | data contains half of a surrogate pair somewhere, such as the JSON "\ud800" or an emoji cut in two |
400 | bad_request | idempotencyKey must be a string of 1-128 characters | The key is empty, longer than 128 characters or not text, or contains half of a surrogate pair, such as the JSON "\ud800" or an emoji cut in two |
400 | bad_request | idempotencyKey cannot contain a null character | The key contains a null character (U+0000) |
404 | not_available | This is not available on this instance yet. | This instance has no webhook subscriptions yet. The body adds feature |
409 | idempotency_key_reused | This idempotencyKey was already used for a different request | Your API key already used this idempotencyKey for a different request: a request of another kind, or this one with other values. Nothing was changed |
409 | idempotency_in_progress | The first request with this idempotencyKey is still running. Try again in a few seconds. | The first request with this key is still being processed. Wait retryAfter seconds, then retry |
409 | idempotency_outcome_unknown | An earlier request with this idempotencyKey may or may not have been applied. It was not sent again. Use a new key to send it again. | A first request with this key was started more than a minute ago and never finished. CEEJAY cannot know whether it took effect, and does not repeat it. Check what happened, then use a new key if it has to be sent again |
503 | idempotency_unavailable | idempotencyKey cannot be used on this community yet, so nothing was changed | You sent a key, and this instance does not support idempotency keys yet. Nothing was changed. Community details reports it as features.retryKeys |
A rejected event is not delivered or stored.
Every error body also carries requestId, and a 400 with the code bad_request names the invalid field in field. Match on code, not on the message text, which can change.
The errors every request shares (401, 403, 405, 429, 500) are on the Errors page.

