The Simiz Provider Protocol is the formal, opt-in, and strict exchange contract between a provider (Mobile Money operator, bank, aggregator) and Simiz. You implement this standard once, certify against the real pipeline, and become routable — without Simiz having to map your proprietary schema.
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 same idempotency_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}
Your handler must
  1. 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.
  2. 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

The X-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 signed payment.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
  1. Verify the signature first, exactly as for the health check (401/403 on mismatch).
  2. Respond HTTP 200 synchronously with a payment.ack echoing the idempotency_key:
To refuse the order (unsupported corridor, blocked payee, …), reply "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.
  1. Execute the payment on your licensed rail, asynchronously.
  2. Push the outcome as a regular inbound SettlementEvent to your Simiz webhook, reusing the same idempotency_key you received (or referencing the same transaction_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 same payment.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_...:
Official minimal worker loop — Node
Pull transport authenticates the provider with the API key and TLS. Webhook transport authenticates each pushed order with 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 dispatch payout.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}
The fields follow the same constraints as payment.execute; only type, the pexo_ idempotency prefix, and the business direction change. Reply synchronously with:
To refuse the payout, return "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

Dispatch diagram: a provider SettlementEvent reaches the Simiz dispatcher; if protocol_version is present, strict schema validation (200 ACK or field-by-field 422); otherwise the existing proprietary adapter.

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).

The dispatcher routes based on the presence of the 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).
No silent switch: adopting the protocol is an explicit choice carried by the payload itself.

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 for SettlementEvent 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:
The response is the complete JSON Schema for the 8 fields above (types, enumerations, constraints). It is the same artifact used by the certification pipeline and by the generated adapter.

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 the payment.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:
1

Success

A valid COMPLETED SettlementEvent is accepted end to end.
2

Failure

A FAILED settlement is processed without incorrectly mutating the transaction.
3

Decimal amount

An amount in major units (for example "1500.00") is accepted; cents are rejected.
4

Idempotent duplicate

The same returned idempotency_key is detected as duplicate (409) and ignored.
5

Status outside vocabulary

An unknown status is rejected with 422 and the invalid field.
6

Non-ISO date

A malformed settled_at is rejected with 422.
7

Invalid signature

A body whose HMAC signature does not match is rejected (403).
8

Outbound — order acked

A signed payment.execute is accepted (payment.ack ACCEPTED with the idempotency_key echoed).
9

Outbound — foreign key echo

An ack echoing a different idempotency_key is treated as a failed delivery — never a false success.
10

Outbound — non-2xx

An endpoint answering HTTP 500 fails the delivery explicitly.
11

Outbound — timeout

A silent endpoint fails with an explicit TIMEOUT.
12

Outbound — signature refused

A handler holding the wrong secret rejects the signature (401) — proving your handler actually verifies.
When all cases are green, your channel carries the CERTIFIED badge. Each red case is diagnosed clearly, with the exact fix to apply.
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).
The adapter above sends events to Simiz. On the receiving side — the baseUrl handler that accepts Simiz’s inbound calls — verify the signature against the raw body, then answer the health check and the payment order:

For AI assistants (Copilot, Claude)

Give this page as-is to your coding copilot, or open it in an assistant with a pre-filled prompt:

Open in ChatGPT

Open in Claude

Or copy the full prompt pack:
Simiz remains non-custodial: the adapter runs on your side, on your rails; Simiz orchestrates.