SALAF EMSDevelopers
salafems.com

Testing

The salaf.ping event and how to develop against it.

There is no sandbox tenant and no test-mode key, so the way to prove a receiver works is to send it a real event that changes nothing: salaf.ping.

Send a test event

In the dashboard: Settings → Developer → Webhooks, open an endpoint, and choose Send test event.

The ping is not a shortcut. It is recorded in the outbox, fanned out, signed with your endpoint's actual secret, delivered by the same worker, retried on the same ladder, and written to the same delivery log as any other event. A ping that arrives is therefore real evidence that an order.created would — and a ping that fails is debuggable with the same detail view, showing the exact payload sent and the response received.

{
  "id": "evt_01J4X8G9ABCDEFGHJKMNPQRSTV",
  "type": "salaf.ping",
  "api_version": "2026-08",
  "created_at": "2026-08-11T10:00:00.000Z",
  "company_id": "e07b4a29-6c13-4d85-b920-3f8e1c65d704",
  "store_id": "b21c5f04-9d7e-4c11-8f3a-6e0d2b915a77",
  "data": {
    "object": {
      "store_id": "b21c5f04-9d7e-4c11-8f3a-6e0d2b915a77",
      "message": "This is a test event from Salaf. If you can read this, your endpoint and signature verification are working.",
      "sent_at": "2026-08-11T10:00:00.000Z"
    }
  }
}

The endpoint must also be active. Sending a test to a disabled or auto-disabled endpoint answers "Enable the endpoint before sending a test event."

Reading the result

The delivery history for an endpoint shows every attempt: status, HTTP code, attempt number, duration, next retry, and the response your server sent (first kilobyte). Open one and you also get the exact payload we signed — byte for byte, which is what makes a signature mismatch diagnosable rather than mysterious.

The failures worth recognising:

What you seeWhat it usually is
No response within 10sThe handler is doing the work inline. Queue it and answer first.
HTTP 400 with your own error textYour signature check is rejecting us. Run the test vectors.
Redirects are not followed…The URL redirects (often httphttps, or a trailing-slash rule). Register the final URL.
A DNS or address errorThe hostname does not resolve publicly. Tunnels and private addresses are refused, deliberately.
HTTP 404The route exists in your framework but not for POST, or a CSRF/auth middleware is in front of it.

Developing locally

Your URL must be public https — we resolve the hostname and refuse private addresses, so localhost, 127.0.0.1 and anything on a private range cannot be registered.

Two approaches that work:

  1. A public tunnel (ngrok, Cloudflare Tunnel, Tailscale Funnel) pointed at your dev server. Register the tunnel's https URL as an endpoint, subscribe it to *, and develop against real events. Remember to delete the endpoint when the tunnel dies, or it will fail for three days and auto-disable.
  2. A replayed payload, offline. Copy the exact payload and the X-Salaf-Signature header from a delivery in the log, and post them at your own handler from a test. This is the one that belongs in your test suite: it needs no network, and it catches the raw-body mistakes that only show up on payloads containing non-ASCII text.

A checklist before you go live

  • The endpoint is subscribed to the events you actually handle, plus salaf.ping.
  • Signature verification runs against the raw body and rejects on failure.
  • The five-minute timestamp window is enforced.
  • Both v1= values are accepted, so a rotation does not take you down.
  • Events are de-duplicated on X-Salaf-Event-Id.
  • Handlers upsert, and tolerate an update arriving before its create.
  • The endpoint answers 2xx in well under 10 seconds and does its work asynchronously.
  • Unknown event types are ignored rather than throwing.