Skip to content

Signup and login

Your webshop already has customer accounts. We have members. The whole integration rests on keeping those two in step: creating a member when a customer joins, and finding the right member when a customer logs in.


There is no required UI — the form below is one reasonable layout, built from the same example member we use throughout. Every field maps to something in the Flow A request body; the two segment questions come straight from the GET /segments example in Core concepts.

Signup form mockup: mobile number, email, name, a checkbox segment question, a radio segment question, and four unticked consent checkboxes — joining the club (required), SMS offers, email offers and personalised offers

A few things this mockup is deliberately explicit about:

  • Every consent box starts unticked. A pre-ticked box is not consent.
  • One box per consent flag. Joining the club maps to consent.gdpr_accepted and is required. SMS and email are separate boxes because they are separate flags, and you must send both — a flag you leave out of the create request defaults to true. Our consent section covers all four.
  • The segment checkboxes and radio are rendered from GET /segments, not hardcoded — this mockup shows the specific options from that chapter’s example response, but your form should render whatever the merchant has configured.
  • The terms link should point at the store’s own privacy_policy_url, not a static page — see consent.

Section titled “The rule: your customer record owns the link”

Store the Diller member_id on your own customer record. That is the join.

your_customers
id 42
email ola@example.com
phone +4799900001
diller_member_id m_nsr714RRBEHX <-- add this column

Everything below exists to populate that column correctly, and to recover when it is empty.

Do not re-derive the member on every request by searching on phone or email. Search is for the moment of linking; after that you have the id.


There are two ways to create a member and they are not interchangeable.

POST /membersPOST /members/enroll
Use whenYou collected consent on your own formYou only have a phone number
Sendsphone, profile, consentphone, department_id
ResultMember is active immediatelyActivation link sent by SMS; member inactive until they accept
Requiresconsent.gdpr_accepted: true—
Typical callerWebshop signup formPOS, or a phone-only capture

For a webshop, use POST /members. Your signup form shows the terms and the consent checkboxes, so consent is already given — there is nothing for an SMS activation step to add, and sending one makes the customer complete signup twice.

POST /members/enroll matters to you in one situation: a customer who was signed up in a physical store and is now finishing on your site. That is the activation token flow.


Flow A — new customer registers on your webshop

Section titled “Flow A — new customer registers on your webshop”

The common case. The customer fills in your form, which includes the loyalty consent checkboxes, and you create the member as part of account creation.

sequenceDiagram autonumber actor C as Customer participant B as Browser participant S as Your server participant D as Diller API participant Y as Your database C->>B: Fills signup form Note over B: Loyalty consent checkboxes<br/>+ segment questions B->>S: POST /register (own origin, CSRF) S->>Y: Create customer account Y-->>S: customer id 42 S->>D: POST /connect/token D-->>S: access_token S->>D: GET /members/search?phone=+4799900001 alt Already a member D-->>S: 200 [ { id: "m_nsr..." } ] S->>D: PUT /members/m_nsr... (full profile + consent) D-->>S: 200 MemberResponse else Not a member D-->>S: 200 [] S->>D: POST /members (profile + consent + segments) D-->>S: 200 MemberResponse { id } end S->>Y: UPDATE customers SET diller_member_id = 'm_nsr...' WHERE id = 42 S-->>B: Account created B-->>C: Welcome, you are a member

Search before you create. The customer may already be a member from an in-store visit. Skipping the search means a duplicate attempt, which we reject with 400 “Member with phone number ending in 0001 is already registered for this store”.

POST /api/v2.0/stores/{storeId}/members HTTP/1.1
Authorization: Bearer {token}
Content-Type: application/json
{
"phone": {
"country_code": "+47",
"number": "99900001"
},
"first_name": "Ola",
"last_name": "Nordmann",
"email": "ola.nordmann@example.com",
"birth_date": "1988-04-12T00:00:00",
"gender": "NotShared",
"address": {
"street": "Karl Johans gate 3",
"city": "Oslo",
"zip_code": "0154",
"state": null,
"country_code": "NO"
},
"department_ids": ["101"],
"consent": {
"gdpr_accepted": true,
"save_order_history": true,
"receive_sms": true,
"receive_email": false
},
"segments": [
{ "segment_id": 633, "selected_options": [4101, 4103] },
{ "segment_id": 2222, "selected_options": [5001] }
],
"origin": {
"system_id": "my-webshop",
"employee_id": null,
"department_id": "101",
"channel": "OnlineStore"
}
}

Only phone is required by the schema, but we also validate first_name, last_name and consent as non-empty, and email as a valid address when present. address.country_code must be a valid ISO alpha-2 code.

Always send receive_sms and receive_email, set from the customer’s own answers. Leave either out and it takes the value of gdpr_accepted — true — which opts the member in to marketing without their say-so.

Letting the customer into the Diller member portal

Section titled “Letting the customer into the Diller member portal”

POST /members also accepts a password, so the customer can later log in to our member pages:

{
"password": "Sommer2026",
"repeat_password": "Sommer2026"
}

Rules: at least 8 characters, at most 64, must include uppercase and numbers. Both fields must match. They are write-only and never returned.

Only send these if you are surfacing the member portal to customers. Otherwise leave them out — you do not want to invent and discard passwords.


On login, ensure the account is linked. If diller_member_id is already set, you are done — no call to us needed.

sequenceDiagram autonumber actor C as Customer participant S as Your server participant Y as Your database participant D as Diller API C->>S: Log in S->>Y: Load customer 42 Y-->>S: diller_member_id = ? alt Already linked Note over S: Nothing to do.<br/>Use the stored member id. else Not linked yet S->>D: GET /members/search?phone=+4799900001 alt Match found D-->>S: 200 [ { id: "m_nsr..." } ] S->>Y: Save diller_member_id else No match D-->>S: 200 [] Note over S: Not a member.<br/>Offer to join — do NOT<br/>enrol them silently. end end S->>D: GET /members/{memberId} D-->>S: points, level, consent S-->>C: Loyalty panel
GET /api/v2.0/stores/{storeId}/members/search?phone=%2B4799900001

Supply exactly one of these parameters:

ParameterUse for
phoneThe primary lookup. International format, +47… or 0047…
emailSecondary lookup
identifierValueYour own customer id — see below
cardIdentifierA loyalty card linked to the member
activationTokenCompleting an in-store signup (Flow C)

If you send a national-format number, we assume the store’s own country. Send international format and remove the ambiguity — and encode it: a raw + in a query string is read as a space, so +4799900001 must go out as %2B4799900001. encodeURIComponent does this, as below.


Phone numbers change. A sturdier link is to store your customer id inside Diller, so either side can find the other.

POST /api/v2.0/stores/{storeId}/members/{memberId}/identifiers
Content-Type: application/json
{ "type_id": 1, "value": "42" }

Read it back with:

GET /api/v2.0/stores/{storeId}/members/search?identifierValue=42

The identifier type is configured per store — GET /stores/{storeId} returns member_identifier, describing the type the merchant has set up. Ask us to configure one for your integration if the merchant has none.


Flow C — finishing a signup started in store

Section titled “Flow C — finishing a signup started in store”

A customer joined at the till. We send them an SMS with an activation link. The link brings them to your site to finish, carrying a token:

https://yourshop.example/join?dref=signup&token=996405d26e29
sequenceDiagram autonumber actor C as Customer participant P as In-store POS participant D as Diller participant B as Browser participant S as Your server participant Y as Your database C->>P: Joins at the till (phone only) P->>D: POST /members/enroll D-->>C: SMS with activation link C->>B: Opens the link B->>S: GET /join?dref=signup&token=996405d26e29 S->>D: GET /members/search?activationToken=996405d26e29 D-->>S: Pending member { id, phone } S-->>B: Prefilled form (phone locked)<br/>+ consent + segments C->>B: Completes and submits B->>S: POST /join S->>Y: Create customer account S->>D: PUT /members/{memberId} (full profile + consent) D-->>S: 200 MemberResponse S->>Y: Save diller_member_id S-->>B: You are all set

Two things to get right:

  1. The phone number is already fixed. Show it, do not let them edit it here. PUT /members/{memberId} has no phone field — changing a number goes through reset-phone, which is a separate verified flow.
  2. A used or unknown token must fail gracefully. Tokens are cleared once consumed. If the search returns an empty array, show your normal signup form rather than an error page.

Someone buys without an account, and your checkout offers to join the loyalty club.

Two honest options:

They tick the consent boxes — create the member with POST /members, then post the transaction against the new member id. They get points for the order they just placed.

They do not tick the boxes — post nothing to us. There is no member and no consent, so there is nothing to attach the order to. Do not create a member “just in case”; that is exactly the thing consent law exists to prevent.


When a customer edits their details on your site, mirror it to us.

PUT /api/v2.0/stores/{storeId}/members/{memberId}

PUT treats a field you leave out differently depending on the field:

Left out of the bodyResult
first_name, last_name, email, birth_date, genderKept
address, department_ids, segmentsCleared
consent.receive_sms, consent.receive_emailSet to false

So read the member first, and build the update body from it — field by field, not by sending the GET response back. Segments in particular come back in a read shape keyed by id, and PUT wants them keyed by segment_id. Sending the read shape returns 400 “Segment ids are duplicated.”

const current = await (await dillerFetch(
`/api/v2.0/stores/${storeId}/members/${memberId}`
)).json();
const toSegmentAnswer = (s) =>
s.type === 'Text' || s.type === 'DateTime'
? { segment_id: s.id, value: s.value }
: { segment_id: s.id, selected_options: s.selected_options };
const updated = {
first_name: current.first_name,
last_name: current.last_name,
email: newEmail,
birth_date: current.birth_date,
gender: current.gender,
address: { ...current.address, street: newStreet },
department_ids: current.department_ids,
segments: current.segments.map(toSegmentAnswer),
consent: current.consent, // all four flags, so neither marketing flag drops to false
origin: { system_id: 'my-webshop', department_id: '101', channel: 'OnlineStore' },
};
await dillerFetch(`/api/v2.0/stores/${storeId}/members/${memberId}`, {
method: 'PUT',
body: JSON.stringify(updated),
});

There is no phone in the body — PUT does not accept one.

Changing a phone number is a different operation — see Members reference.


stateDiagram-v2 [*] --> Pending: POST /members/enroll<br/>(activation SMS sent) [*] --> Active: POST /members<br/>(consent already given) Pending --> Active: Member accepts terms<br/>PUT /members/{id} Pending --> Pending: Re-enrol resends the link Active --> Active: PUT /members/{id}<br/>(update) Active --> Unsubscribed: receive_sms / receive_email<br/>false or left out of a PUT Unsubscribed --> Active: Consent given again Active --> MarkedForDeletion: DELETE /members/{id} Unsubscribed --> MarkedForDeletion: DELETE /members/{id} MarkedForDeletion --> [*]: Purged after 10 days note right of Pending Not yet a full member. gdpr_accepted is false. end note note right of Unsubscribed Still a member. Marketing consent withdrawn, membership intact. end note

Unsubscribed is not deleted. Turning off receive_sms and receive_email stops marketing; the member keeps their points, coupons and history. Only DELETE removes them, and even then they are marked and purged after ten days rather than erased on the spot.


  • diller_member_id column on your customer record
  • Search before create, on every signup
  • Handle the empty-array miss, not just the HTTP status
  • consent.gdpr_accepted: true, from a real checkbox the customer ticked
  • receive_sms and receive_email sent explicitly on create, from the customer’s answers
  • origin.channel: "OnlineStore" on every call
  • Segment form rendered from GET /segments, not hardcoded
  • PUT built from a fresh read: both marketing flags, address, departments, and segments mapped to segment_id
  • Never enrol on login without an explicit opt-in
  • The merchant’s terms and privacy_policy_url shown at the point of consent