Simiz is non-custodial orchestration: the provider executes payments on its own licensed
rails; Simiz orchestrates routing, monitoring, and reconciliation. Simiz does not hold funds
and does not host your adapter.
Webhook or Pull API?
Choose the transport per rail. Both carry the same protocol payloads and the sameidempotency_key; only delivery changes.
You can run one rail in Webhook mode and another in Pull mode. Do not implement both transports for the same rail unless an operator explicitly opens a migration window; transport changes are audited and the rail must be re-tested before it routes again.
Health check (Simiz → you)
The protocol has two outbound order transports. With Webhook, Simiz sends signed calls to your execution endpoint (baseUrl) — the health probe below, the payment.execute order, and, when payouts are enabled, payout.execute. With Pull API, Simiz stores the same order payloads and your system fetches them from /v1/provider/orders.
For Webhook rails, when you (or a Simiz operator) click Test rail in the portal, Simiz sends a signed POST to the same execution endpoint (baseUrl) you declared in the portal, to prove it holds the rail webhook secret and that your rail answers. The rail is marked TESTED only when your handler replies HTTP 200 with the matching nonce echoed back. Anything else — non-2xx, a wrong or missing nonce, an unreachable host, or a non-HTTPS endpoint — fails the probe, and the rail stays PENDING and cannot go live. Pull API rails do not declare baseUrl; their orders are fetched by API key.
Request — Simiz → POST {baseUrl}
- Verify the signature exactly as for inbound events (same scheme, same rail webhook secret
whsec_…; see signature). Reject with 401 or 403 if it does not match — this is how you confirm the call really comes from Simiz. - Respond HTTP 200 with the nonce echoed back:
Constraints: the endpoint must be HTTPS, the probe times out at ~5 s, and Simiz marks the
rail TESTED only on
200 + a nonce that matches the one it sent. Echo the exact nonce
you received — a wrong or missing nonce is treated as a failed probe.Signature
TheX-Simiz-Signature header uses HMAC-SHA256 for Webhook transport. Simiz signs (and you verify) the exact string `{timestamp}.{rawBody}` — the X-Simiz-Timestamp value, a literal dot, then the raw request body verbatim — with your rail’s webhook secret (whsec_…), and encodes the digest as hex (the v1= part of the header). This is the generateWebhookSignature scheme; verify against the raw body before parsing JSON. Pull orders are authenticated by provider API key instead; settlement events you push back to Simiz use your provider webhook/channel signing secret.
Payment execution (Simiz → you)
With Webhook transport, when the Simiz router selects your rail for a payment, Simiz sends a signedpayment.execute order to the same execution endpoint (baseUrl), over the same envelope as the health check (same headers, same signature scheme, same shared secret — only X-Simiz-Event changes to payment.execute). With Pull API, fetch the same payload from /v1/provider/orders.
Request — Simiz → POST {baseUrl}
Field constraints
Your handler must
- Verify the signature first, exactly as for the health check (401/403 on mismatch).
- Respond HTTP 200 synchronously with a
payment.ackechoing theidempotency_key:
"status": "REJECTED" with an optional human-readable "reason". Anything else — non-2xx, an unparseable body, a wrong or missing idempotency_key echo — is treated as a failed delivery and the payment is not considered accepted.
- Execute the payment on your licensed rail, asynchronously.
- Push the outcome as a regular inbound
SettlementEventto your Simiz webhook, reusing the sameidempotency_keyyou received (or referencing the sametransaction_ref). That settlement event — not the ack — is the truth of the outcome: it is what completes or fails the transaction on the Simiz side.
The ack means “accepted for execution”, never “paid”. The protocol has no status-poll
endpoint: the settlement event you push is the single source of truth for the outcome, and the
idempotency_key is the thread that ties order, ack, and settlement together. Deliveries may be
retried with the same idempotency_key — treat a key you have already seen as a duplicate and
do not execute twice. Simiz retries transient delivery failures (timeout, 502, 503, 504) up to
3 attempts with the same idempotency_key.Pull transport (you → Simiz)
If you cannot expose a public HTTPS endpoint, choose Pull API as the rail’s Order transport in the portal. The message is still the samepayment.execute or payout.execute payload, but your system fetches it from Simiz with a provider API key instead of receiving a signed webhook POST.
Use a provider API key with Authorization: Bearer sk_...:
X-Simiz-Signature. Both transports deliver the same v1 payload and use the same idempotency_key correlation. If an order is never pulled or acked before expires_at, Simiz expires it and fails the correlated transaction or payout.
Payout execution (Simiz → you)
When a rail is marked Supports payouts in the provider portal, Simiz can dispatchpayout.execute orders through the rail’s selected transport: signed POST to baseUrl for Webhook, or /v1/provider/orders for Pull API. This is the outbound money movement contract for salaries, marketplace seller payouts, agent commissions, and refunds that leave a merchant balance.
Request — Simiz → POST {baseUrl}
payment.execute; only type, the pexo_ idempotency prefix, and the business direction change. Reply synchronously with:
"status": "REJECTED" with a "reason". Simiz never dispatches a payout before its own balance, idempotency, environment, and supportsPayout gates pass; your ack only confirms that your rail accepted the order for asynchronous execution. The final truth still comes back as a SettlementEvent using the same idempotency_key.
Routing
The dispatcher routes the event by the presence of the protocol_version field: the payload is strictly validated (200 ACK on success, field-by-field 422 on violation); a payload without the field follows your existing proprietary adapter, unchanged. Simiz orchestrates, the provider executes (non-custodial).
protocol_version field in the event body:
- payload with
protocol_version: "1"→ strict schema validation, field-by-field 422 rejection on any violation; - payload without
protocol_version→ your existing proprietary adapter (mapping unchanged).
SettlementEvent payload
The v1 settlement event pushed to your webhook carries exactly these fields:
Field constraints
status vocabulary
network vocabulary
The body is signed with HMAC-SHA256 (
x-simiz-signature header). Any violation returns a 422 listing every invalid field — never a silent failure.
JSON Schema
The JSON Schema forSettlementEvent v1 is the source of truth against which Simiz validates, from which the one-paste adapter is generated, and which you can give to your own validator or code generator.
From the authenticated provider portal, retrieve it as-is:
Certification
From the portal (Integration → Compliance → Certify now), Simiz runs 12 canonical cases through the real pipeline — 7 inbound cases on your settlement events, then 5 outbound cases proving thepayment.execute contract against a reference handler. If a TESTED or ACTIVE rail has Supports payouts enabled, Simiz adds 5 payout outbound cases, so payout-capable certification is 17 cases:
Outbound — order acked
A signed
payment.execute is accepted (payment.ack ACCEPTED with the idempotency_key
echoed).Outbound — foreign key echo
An ack echoing a different
idempotency_key is treated as a failed delivery — never a false
success.Payout-capable certification adds the same five delivery checks for
payout.execute: acked,
foreign idempotency_key, non-2xx response, timeout, and signature refused. Collection-only
providers remain certifiable with the original 12 cases.One-paste adapter
The portal generates a Node or Python adapter (~30 lines) that maps your raw data to the v1 payload, signs the body (sha256 hex), and posts it to your Simiz webhook. You deploy it on your side — Simiz does not host, execute, or store anything.The portal can also pre-fill this adapter from a real sample: paste a CSV export or JSON
record, Simiz infers which of your fields correspond to the 8 protocol fields and returns the
pre-mapped adapter — unresolved fields remain marked
// TODO (no silent guessing).baseUrl handler that accepts Simiz’s inbound calls — verify the signature against the raw body, then answer the health check and the payment order:

