Pick one or more. Each transport is its own connection with its own source_id.
6.1 Webhook
POST https://<api host>/promo/v1/ingest/<source_id>
Content-Type: application/json
X-Promo-Timestamp: <unix seconds>
X-Promo-Signature: <signature>
signature = lowercase_hex( HMAC-SHA256( secret, X-Promo-Timestamp + "." + raw_body ) )
- Sign the exact bytes you send. Serialising the body again after signing (another key order, spaces) breaks the signature.
X-Promo-Timestampmust be within ±300 s of our clock.- Rotation. During a rotation we accept both the current and the next secret: we give you the next one, you switch to it, we retire the old one.
- Body. One event, or
{"events": [ … ]}with up to 500 events; at most 1 MiB. Other limits can be agreed in the form. - Concurrency. The number of requests in flight is agreed in the form; a request that finds no free slot within 0.5 s is answered
503.
| Status | Body | Meaning | Resend? |
|---|---|---|---|
200 | {"source":"<source_id>","results":[{"index":0,"event_id":"…","verdict":"accepted"}, …]} | every event is decided; verdict is accepted, skipped, rejected or duplicate; skipped and rejected carry a reason (Appendix A). A duplicate may carry one too — ignore it, the event was decided earlier | no |
503 + Retry-After: 1 | {"source":"<source_id>","error":"undecided","results":[ …decided so far… ]} | we could not decide an event right now; events after it were not tried | yes — the same batch, the same event_ids |
401 | {"error":"unauthorized","results":[]} | missing or stale timestamp, missing signature, or a signature that matches no secret | after fixing the request |
413 | {"error":"body_too_large","results":[]} / {"error":"batch_too_large","results":[]} | over 1 MiB, or more events than the batch limit | after splitting |
400 | {"error":"malformed","results":[]} | signed, but not a JSON object, or events is not an array | after fixing |
404 | {"error":"unknown_source","results":[]} | no active connection with this source_id | contact us |
On 503, any other 5xx or a network error, resend the same batch: events already decided answer
duplicate, the rest are decided. {"events": []} answers 200 with empty results. If a connection keeps answering 503, contact us.
Examples (standard library only), each sends one file from examples/events/:
export PROMO_INGEST_URL=https://api.rexplay.site/promo/v1/ingest/<source_id>
read -rs PROMO_SOURCE_SECRET && export PROMO_SOURCE_SECRET
cd examples/webhook
go run . ../events/bet.json
python3 send.py ../events/settled.json
200 {"source":"<source_id>","results":[{"index":0,"event_id":"01J8Z3K9T6-bet-778812","verdict":"accepted"}]}
6.2 Kafka (your cluster)
We connect to your cluster as a consumer.
- You give us (form, and credentials once — §10.2): the bootstrap brokers, every broker address the cluster advertises with its IP, the topics, TLS — the CA, and the client certificate with its key for mutual TLS, plus the TLS server name — and SASL (
PLAIN,SCRAM-SHA-256orSCRAM-SHA-512) with a user that may read the topics and use our consumer group. We send you the group name for your ACLs. - TLS is required in production.
- Message. The record value is one event (§5.2), JSON in UTF-8. The key and the headers are not read: key the topic as your own ordering needs.
- Start. We read from the moment we connect; tell us in the form if we should also read what the topic already retains.
- Delivery. We commit an offset only after its event is decided. An event we could not decide is read again, so delivery is at least once;
event_idabsorbs the repeats.
6.3 RabbitMQ (your broker)
AMQP 0-9-1 over TLS (amqps://, required in production). You give us the host, port, vhost, user and password, and one of:
-
an exchange and routing keys — we declare our own durable queue and bind it to your exchange. You declare the exchange; our user needs the right to declare and bind that queue. The queue holds only what arrives after we create it; or
-
a queue you created for us — our user needs the right to consume it.
-
Message. The body is one event (§5.2). Headers and properties are not read. Publish persistently so an event survives a broker restart.
-
Delivery. We acknowledge a message only after its event is decided. One we could not decide stays unacknowledged and we retry it after 1 s, doubling up to 30 s; we never requeue it. If our connection drops, the broker redelivers what we had not acknowledged, and a decided event answers
duplicate. Several of our consumers may read one queue; order is not required.