Every SDK publishes through one HTTPS endpoint. Most applications use a generated client and never call it directly, but it is all you need from a language without an SDK, a script, or curl.
Publish an event#
curl -X POST https://api.repost.sh/v1/messages \
-H "Authorization: Bearer $REPOST_SEND_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1001" \
-d '{
"type": "order.created",
"customerId": "acme",
"data": { "id": "order-1001", "currency": "EUR" }
}'| Field | Required | Meaning |
|---|---|---|
type | Yes | The event type, as deployed from your schema (order.created). |
customerId | Yes | The customer's externalId. |
data | Yes | The payload, shaped by the event's model. |
timestamp | No | RFC 3339 (2026-07-02T12:00:00Z). Defaults to now. |
Authentication is a Bearer token with the publish scope. The Idempotency-Key header (up to 255 characters) is optional but recommended; it is what makes retries safe.
A successful publish returns 202 Accepted with the message:
{
"id": "msg_01JZX3NV7Q9WD9F2M6K4T8RSAB",
"type": "order.created",
"customerId": "acme",
"timestamp": "2026-07-17T12:00:00.000Z"
}202 means Repost has stored the event durably and will deliver it. Fan-out, signing, and retries happen after this point and are covered under delivery.
Idempotency#
To make sure an event is sent exactly once even when your side crashes and retries, set an idempotency key that names the business fact, such as order-1001-created, rather than a random value per attempt. Repost remembers each key for 10 minutes. The key alone identifies the publish; the body is not compared:
- The same key again within those 10 minutes answers
409with the first message's id, and the body of the second call is discarded — changed bytes included. - The same key while the first publish is still in flight is held for up to 6 seconds and then answered the same way.
- After 10 minutes the key is forgotten. The same key then publishes a new message with a new id, so a retry that arrives later than that delivers the event a second time.
A 409 names the message that already exists. Read the id and treat the call as done:
{ "error": "duplicate", "id": "msg_01JZX3NV7Q9WD9F2M6K4T8RSAB" }With stable keys, queue redeliveries, crashed workers, and timeout-retry loops inside that window all collapse into a single delivered event. The SDKs already do this: they mint one key per send, reuse it across their own retries, and turn the 409 into the original result.
Status codes#
| Status | Meaning | What to do |
|---|---|---|
202 | Accepted. | Done. |
400 | Missing or invalid field, bad timestamp, oversized key, or invalid JSON. | Fix the request. |
401 / 403 | Bad token, or missing the publish scope for this environment. | Fix credentials. |
409 | This idempotency key already published; the body carries that message's id. | Done. Use the id. |
413 | Payload over 256 KiB (the same cap as the Receive webhook receiver). | Send references instead of blobs. |
429 | Environment rate limit (Retry-After: 1). | Back off and retry. |
503 | Temporarily unable to accept messages (Retry-After: 2). | Retry with backoff, same key. |
Retry 429, 503, and network failures, always with the same idempotency key. 409 is not a failure: the event is already published. The other errors indicate a problem in the request itself.