Skip to main content

Partner Onboarding

The Partner Onboarding API lets an issuing bank push a customer onboarding event to Ottu — for example, when a cardholder is enrolled in a co-branded card programme and granted a welcome reward. Ottu records the event and returns a reference. Nothing is charged, no wallet is credited, and no message is sent to the cardholder: the endpoint is a receipt, and what happens next (reward issuance, redemption) is handled by the programme it feeds — see Loyalty.

It is a server-to-server call, authenticated with a private API key. The customer never calls it, and it must never be called from a browser.

When to Use​

Use this endpoint when a partner — typically a bank — needs Ottu to know that a customer has been onboarded, so that a downstream programme can act on it. The canonical case is a co-branded card with a welcome voucher: the bank enrols the cardholder, then tells Ottu who was enrolled and which reward they were granted.

Do not use it to:

  • Take a payment or hold funds — use the Checkout API.
  • Credit a customer wallet — see M-Wallet.
  • Store a card for later charging — see Tokenization.
  • Send the customer a notification — the endpoint does not message anyone.

Setup​

  1. Get a private API key for the Ottu installation the partner will call. Keys are created in the Ottu admin (API keys → Add) and shown once. Share the key with the partner over a secure channel — never in a ticket, an email, or a chat.
  2. Give the partner the endpoint — POST /b/onboarding/v1/user/ on your installation host, over HTTPS only.
  3. Agree the reward_type codes. The endpoint accepts any well-formed code the partner sends (lowercase letters, digits, ., _, -); the codes Ottu already recognises get a display name in the dashboard. Today that is welcome_voucher → Welcome Voucher / قسيمة ترحيبية.
The private key is the only credential that opens this endpoint

The public (SDK) key, HTTP Basic credentials and dashboard sessions are all refused with 401. Treat the private key as a secret: it authorises writes to your installation.

Guide​

Record an onboarding event​

The minimal call. Replace <PRIVATE_API_KEY> and the body with the partner's data.

Record a customer onboarding
curl --location 'https://sandbox.ottu.net/b/onboarding/v1/user/' \
--header 'Authorization: Api-Key <PRIVATE_API_KEY>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: 3f2a9c1e-4b7d-8e6f-0a1b-2c3d4e5f6071' \
--data '{
"customer_first_name": "Saifuddin",
"customer_last_name": "Tahir",
"customer_phone": "+96597117060",
"cardholder_masked_pan": "316516xxxxxxxx1556",
"reward_type": "welcome_voucher"
}'

A successful first call answers 201 Created:

{
"reference": "9b1c2d3e-4f50-4a6b-8c7d-8e9f0a1b2c3d",
"status": "received"
}

reference is Ottu's identifier for the recorded event — store it against the customer on your side. status is always received.

The card number is never returned

The response carries only the reference and the status. cardholder_masked_pan — even masked — is not echoed back, and it is redacted from Ottu's logs.

Make the call safe to retry​

Networks drop responses. If the partner resends a request after a timeout without any protection, Ottu would record the same customer twice. Send an Idempotency-Key header — an opaque id you generate, such as a UUID — and the endpoint behaves like this:

The partner sendsOttu answers
A key for the first time201 — recorded; a new reference
The same key, same body again200 — the original reference; nothing recorded again
The same key, different body409 — refused, so a recycled key cannot silently discard a customer

Without the header, every call records a new event.

A retry with the same key returns the original reference
# first attempt (response lost in transit)
curl -s -X POST 'https://sandbox.ottu.net/b/onboarding/v1/user/' \
-H 'Authorization: Api-Key <PRIVATE_API_KEY>' -H 'Content-Type: application/json' \
-H 'Idempotency-Key: 3f2a9c1e-4b7d-8e6f-0a1b-2c3d4e5f6071' \
-d '{"customer_first_name":"Saifuddin","customer_last_name":"Tahir","customer_phone":"+96597117060","reward_type":"welcome_voucher"}'
# -> 201 {"reference": "9b1c...2c3d", "status": "received"}

# the retry
curl -s -X POST 'https://sandbox.ottu.net/b/onboarding/v1/user/' \
-H 'Authorization: Api-Key <PRIVATE_API_KEY>' -H 'Content-Type: application/json' \
-H 'Idempotency-Key: 3f2a9c1e-4b7d-8e6f-0a1b-2c3d4e5f6071' \
-d '{"customer_first_name":"Saifuddin","customer_last_name":"Tahir","customer_phone":"+96597117060","reward_type":"welcome_voucher"}'
# -> 200 {"reference": "9b1c...2c3d", "status": "received"} (same reference)
The key must not contain a card number

The Idempotency-Key is stored and searchable, so a value that contains a 13–19 digit card number is refused with 400 — Must not contain a card number. Use an opaque id such as a UUID. Generate the key; never derive it from the PAN.

Send only a masked card number​

cardholder_masked_pan is optional. When you send it, it must be the card number masked to its first six and last four digits — the PCI DSS display form. The mask character can be x, X, * or #:

316516xxxxxxxx1556 ✓
411111******1111 ✓
411111######1111 ✓
4111111111111111 ✗ a full card number — refused
4111111xxxxxxxx1 ✗ seven digits in a row — refused
A full card number is refused, by design

Ottu does not store full card numbers on this endpoint. Any value with a run of seven or more digits, or more than ten visible digits in total, is rejected with 400 and the message Must be a masked card number showing at most the first six and last four digits. — even if the partner sent it by mistake. Mask before you send.

API Reference​

Endpoint​

MethodPOST
Path/b/onboarding/v1/user/
AuthAuthorization: Api-Key <PRIVATE_API_KEY> — private key only
Content typeapplication/json (request and response)
Rate limit60 requests per minute per API key; 429 when exceeded

Headers​

HeaderRequiredDescription
AuthorizationYesApi-Key <PRIVATE_API_KEY>. The public key, HTTP Basic and sessions are refused.
Content-TypeYesMust be application/json; anything else is 415.
Idempotency-KeyNoYour opaque id for the request — letters, digits and . _ : -, up to 64 characters. Same key + same body → 200 with the original reference; same key + different body → 409. Must not contain a card number.

Request body​

FieldTypeRequiredDescription
customer_first_namestring (≤64)YesThe cardholder's first name. Control characters are not allowed.
customer_last_namestring (≤64)YesThe cardholder's last name. Control characters are not allowed.
customer_phonestring (≤20)YesMobile number in international format, e.g. +96597117060. Stored normalised to E.164, so +965 9711 7060 is accepted and stored as +96597117060.
cardholder_masked_panstring (≤25)NoThe card number masked to its first six and last four digits, e.g. 316516xxxxxxxx1556. Digits and the mask characters x * # only. A full card number is refused. Never returned.
reward_typestring (≤64)YesThe reward code, e.g. welcome_voucher — letters, digits and . _ -. Any well-formed code is accepted and stored as sent; known codes get a display name in the dashboard (welcome_voucher → Welcome Voucher / قسيمة ترحيبية).

Unknown fields in the body are ignored.

Response body​

KeyTypeDescription
referencestring (UUID)Ottu's identifier for the recorded event. Store it.
statusstringAlways received.

Status codes​

StatusMeaningWhat to do
201 CreatedThe event was recorded.Store reference.
200 OKThe same Idempotency-Key was sent before with the same body.Treat as success; reference is the original one.
400 Bad RequestA field is missing or invalid, or the card number is not properly masked.Fix the request — the body names the field. See the error table below.
401 UnauthorizedThe API key is missing, wrong, revoked, or is the public key.Check the Authorization header and the key.
405 Method Not AllowedAnything but POST.Use POST.
409 ConflictThe same Idempotency-Key was sent before with different data.Send a new key, or resend the original body unchanged.
415 Unsupported Media TypeThe body was not application/json.Set Content-Type: application/json.
423 LockedThe installation is paused for maintenance.Retry later; nothing was recorded.
429 Too Many RequestsMore than 60 requests in a minute for this key.Honour Retry-After and back off.

Error messages​

Validation errors are keyed by the field (or header) that failed, in the standard Ottu shape:

{ "cardholder_masked_pan": ["Must be a masked card number showing at most the first six and last four digits."] }
Field / headerMessageCause
any required fieldThis field is required.The field is missing.
any string fieldThis field may not be blank.The field was sent empty. Omit an optional field rather than sending "".
customer_first_name, customer_last_name, reward_typeControl characters are not allowed.The value contains a control byte.
customer_phoneThe phone number entered is not valid.Not a valid international number — include the country code.
cardholder_masked_panOnly digits and the mask characters x * # are allowed.Spaces, dashes or letters other than x/X in the value.
cardholder_masked_panMust be a masked card number showing at most the first six and last four digits.A full or under-masked card number.
reward_typeOnly letters, digits and the characters . _ - are allowed.A display name (Welcome Voucher) or other non-code value; send the lowercase code.
Idempotency-KeyOnly letters, digits and the characters . _ : - are allowed.Spaces or other characters in the key.
Idempotency-KeyMust not contain a card number. Use an opaque id such as a UUID.The key contains a 13–19 digit card number.
detailThis Idempotency-Key was already used for a different onboarding. Send a new key, or resend the original data unchanged.409 — key reuse with different data.
detailNull characters are not allowed.400 — a NUL byte anywhere in the request.

Best Practices​

  • Always send an Idempotency-Key, generated per onboarding event (a UUID is ideal). It is the only thing that makes a retry after a timeout safe.
  • Mask the card number before it leaves the partner's system. The endpoint refuses a full PAN, but the right place to mask is at the source — the value should never travel unmasked.
  • Treat 200 as success. It means the event was already recorded from an earlier attempt; the reference is the same one.
  • On 409, stop and look. It means a key was reused for a different customer — usually a bug in key generation on the partner side, not something to retry.
  • Back off on 429 and 423. Both are temporary; both carry nothing recorded. Retry with the same Idempotency-Key.
  • Keep names and phone in the partner's canonical form. Ottu normalises the phone number to E.164 but stores names exactly as sent.

FAQ​

Does this endpoint issue the reward, credit points, or notify the customer? No. It records that the onboarding happened and returns a reference. Reward redemption is part of the Loyalty programme it feeds.

Can the customer call this from the checkout page or an app? No. It is server-to-server only and requires the private API key, which must never be shipped to a browser or a mobile app.

What if the bank does not have an idempotency key? The header is optional. Without it, each call records a new event — the partner is then responsible for not resending.

Can the same card be onboarded more than once? Yes. A masked card number is not an identity (a re-issued card, or two cards sharing the first six and last four digits, produce the same value), so it is stored as data, not used to deduplicate. Use Idempotency-Key for that.

Why was my numeric reference id refused as the Idempotency-Key? Any 13–19 digit run that passes the card-number checksum is refused, because Ottu cannot tell it apart from a real card number. Use an opaque id such as a UUID, or a prefixed value like NBK-2026-09-11-000123.

Is the endpoint in the interactive API reference? It is published in the public OpenAPI schema of your installation; the interactive reference on this site is regenerated periodically and will include it on the next update.

What's Next?​

  • Authentication — how private API keys work and how to keep them safe
  • Loyalty — the reward programme an onboarding event feeds
  • Error codes — the platform-wide error reference
  • Checkout API — when the customer actually pays
  • Webhooks — how Ottu tells your server about payment outcomes