Setting up an endpoint
Register an endpoint in the fleet portal under Developers → Webhooks, or withPOST /webhook_endpoints (scope webhooks:write):
eventslists the event types to send, or["*"]for all of them, including types added in future.- The response includes the endpoint’s signing
secret(whsec_…). It is shown only on create and on rotate, so store it now. - A fleet can have up to 10 endpoints in each environment. Change
url,events,descriptionorstatus(enabled/disabled) withPATCH /webhook_endpoints/{endpointId}.
Sandbox and live endpoints
Endpoints belong to the environment of the key that registers them, like everything else (see Authentication):- An endpoint registered with a sandbox key (
cxk_test_…), or under the sandbox view of Developers → Webhooks in the portal, receives only sandbox events. - An endpoint registered with a live key (
cxk_live_…) receives only live events.
livemode: true for live, false for the sandbox. Sandbox and live endpoints, events and the event log are separate, so GET /events with a sandbox key lists only sandbox events. When you go live, register your endpoints again with a live key; they get new signing secrets. If you point both at the same URL, branch on livemode.
URL requirements
ChargeExpress refuses URLs that could reach private networks:https://only, on port 443 or 8443.- No username or password in the URL.
- The host must resolve to public IP addresses. Private, loopback, link-local, carrier-grade NAT and cloud metadata ranges (IPv4 and IPv6) are refused, as are
localhost,*.localand*.internal.
What a delivery looks like
Each delivery is an HTTPPOST with a JSON event body:
Responding
- Return any 2xx status within 10 seconds to acknowledge the delivery. The response body is ignored, apart from the first 1 KB being kept in the delivery log.
- Anything else is a failure: a non-2xx status, a timeout, a connection, DNS or TLS error, or a redirect. Redirects are never followed, so register the final URL.
- Acknowledge first and process afterwards. Put the event on a queue and return
200straight away; slow handlers cause timeouts and duplicate deliveries.
Retries
A failed delivery is retried with increasing delays. The first attempt is immediate; after a failure the next attempts wait:
That is 8 attempts over about 45 hours. If the 8th attempt fails, the delivery is marked
failed and not retried automatically. You can still redeliver it.
Delivery attempts are made within a few seconds of their scheduled time.
Disabled endpoints
If every delivery to an endpoint has failed for 3 days in a row (counted from the first failure after its last success), ChargeExpress:- disables the endpoint (
status: "disabled",disabled_reason: "failing"), - marks its pending deliveries
failed, and - emails the fleet’s managers.
PATCH /webhook_endpoints/{endpointId} and {"status": "enabled"}. Re-enabling doesn’t resend what was missed: use redelivery for that.
failing_since on the endpoint shows when the current run of failures started, so you can alert on it before the endpoint is disabled.
Ordering and duplicates
Delivery is at least once and unordered:- The same event can arrive more than once, for example after a timeout where your server did process the request. Dedupe on the event
id(or theX-ChargeExpress-Event-Idheader). Store ids you’ve processed for at least a few days. - Events can arrive in a different order from the one they happened in, especially when retries are involved. A
session.completedcan arrive before itssession.started.
- Use
created_atto decide whether an event is newer than what you already have. - Treat
data.objectas a snapshot of the object when the event was created. If you need the current state, fetch it (GET /sessions/{sessionId},GET /drivers/{driverId}, …). - Make handlers idempotent: applying the same
session.completedtwice should change nothing.
Testing an endpoint
Send test in the portal, orPOST /webhook_endpoints/{endpointId}/test, sends one signed event to the endpoint immediately and returns what your server answered (event_id, type, succeeded, response_status, response_body, error, duration_ms).
By default the event is a ping. Send {"type": "<event type>"} to send any event type instead, with a realistic sample payload of that type:
livemode set for the endpoint’s environment. Test events aren’t stored, retried or listed in /events, so a test doesn’t affect your reconciliation. To exercise the real sequence of events for a session or top-up, use the sandbox.
Rotating the signing secret
POST /webhook_endpoints/{endpointId}/rotate_secret (or Rotate secret in the portal) returns a new secret. For the next 24 hours ChargeExpress signs every delivery with both the new and the previous secret, so the signature header carries two v1= entries:
v1 match my secret” keeps working throughout. Deploy the new secret within 24 hours; after that only the new secret signs. The verification samples accept several secrets, so you can also configure both during the switch.
The event log
Every event (except test events) is stored for 30 days, whether or not any endpoint was subscribed to it.GET /eventslists them, newest first, paginated. Filter withtype(comma-separated, for exampletype=session.completed,wallet.funded),fromandto.GET /events/{eventId}returns one event with itsdeliveries(per endpoint:status,attempts,next_attempt_at,last_response_status,last_response_body,last_error) and adelivery_summary.
Redelivering events
POST /events/{eventId}/redeliver with {"endpoint_id": "we_…"} queues the event for that endpoint again, with a fresh set of 8 attempts. The endpoint must be enabled, or you get 409 conflict. It works for any event in the last 30 days, including events the endpoint wasn’t originally subscribed to, which makes it useful for backfilling.
A redelivery sends the same event, with the same id and content, so your deduplication will recognise it. The signature header is new.
Endpoints receive only events created after they were added. To give a new endpoint history, list
/events and redeliver the ones you need.
