Best practices
The pieces that turn a working integration into a production-grade one.
Everything below is documented in full elsewhere in this reference — this page is the checklist that ties it together. If you only read one section, read Check the receiver first and Always send an idempotency key; they're the two mistakes that are expensive to make in production and free to avoid.
Check the receiver first
Before calling Create an invoice, call
Participant lookup on the buyer's identifier. A business that isn't
registered on Peppol yet — or hasn't published the capability you need — will fail delivery after
you've already committed an invoice number, which is harder to unwind than a lookup that returns
registered: false. Lookups are cheap and cached briefly on SUMU's side; there's no penalty for checking
routinely rather than only when a send fails.
Always send an idempotency key
Every POST that creates something accepts an Idempotency-Key header — see
Idempotency. An invoice number, once issued, is permanently claimed. Without
a key, retrying a request whose response you never saw either double-sends or fails with a
409 CONFLICT_ERROR on the duplicate invoice number, and neither is cleanly recoverable after the
fact. Generate the key once per logical send (e.g. from your own order/invoice reference), not once
per HTTP attempt.
Validate before you build the real thing
Validate runs the full Oman rule set against a UBL document without sending
anything, doesn't count against your send quota, and is safe to call as often as you like. Wire it into
CI or a pre-flight check in your own pipeline rather than discovering schematron failures in production
— they come back as a plain 400 VALIDATION_ERROR, same as a missing field, so catching them early is
on you, not on a distinct error code.
Handle errors by code, and back off correctly
Work from the full error table rather than branching on HTTP status alone — the
API returns a small set of generic codes, and message is where the specific reason lives. A
409 CONFLICT_ERROR on registering a taxpayer needs a completely different response (go finish the
Fawtara association) than a 409 CONFLICT_ERROR on creating one with a vat_id you already have (go
retrieve the existing record) — read the message to tell them apart. On 429, respect the
Retry-After header and back off exponentially rather than retrying immediately — see
Rate limits. On 502 UPSTREAM_ERROR, retry later; it means Peppol or the Tax
Authority is unreachable, not that your request was wrong.
Track delivery deliberately
Every send gives you a transaction_id immediately, before delivery or tax reporting have happened —
see why there are two separate outcomes. Don't
poll Get a transaction in a tight loop; either register a webhook (see
below) or poll on a sane interval. If you do both, treat the webhook as authoritative and use polling
only as a reconciliation fallback, not as your primary signal.
Verify webhook signatures
Every webhook delivery carries an X-Sumu-Signature header — an HMAC-SHA256 of the timestamp and raw
body, keyed with the secret shown once at registration. Webhooks has the full
verification code. Treat an unverified webhook body as untrusted input; don't act on it without checking
the signature first.
Respect rate limits, and plan bulk sends around the reporting window
Limits apply per API key, shared across every taxpayer on the Platform track — see Rate limits for the actual numbers. There's no batch-send endpoint today, so a month-end run is many individual calls: spread it over the hour rather than bursting it, since Oman requires tax reporting within 15 minutes of issuing a B2B invoice. Talk to SUMU before a run above 10,000 documents.
Keep credentials out of source control
Load your API key from an environment variable or a secrets manager, never a committed file. Rotate by creating a new key and deploying it before revoking the old one — see Authentication for the multi-key rotation model. If you're on the Platform track, also read that page's callout on the current access-control gap: one key today has full access to every taxpayer under it.
Test against sandbox before going live
sumu_test_ keys run the same schematron/UBL pipeline as production, just against a non-production
host and a lower rate limit — see Sandbox for the full breakdown. Point your
integration tests at sandbox rather than mocking SUMU's API yourself where possible; it's more likely
to catch a real Oman validation rule than a hand-written stub is — but don't treat it as consequence-free
by default.