API & webhooks

Σύνδεσε ταμείο, e-shop ή οποιοδήποτε εργαλείο με το Membly. Διαθέσιμο στο πακέτο Enterprise.

Ξεκίνημα

Ο ιδιοκτήτης του καταστήματος φτιάχνει κλειδί από Ρυθμίσεις → API & webhooks και σου το δίνει. Εμφανίζεται μία φορά — κρατάμε μόνο τη σύνοψή του.

Κάθε αίτημα το φέρει στην κεφαλίδα Authorization. Τα σώματα είναι JSON.

curl https://membly.gr/api/v1/products \
  -H "Authorization: Bearer mbl_live_..."

Ο πελάτης αναγνωρίζεται από τον σειριακό της κάρτας του — αυτόν που είναι τυπωμένος πάνω της ως barcode και QR. Δεν χρησιμοποιούμε εσωτερικά αναγνωριστικά προς τα έξω: ο σειριακός είναι σταθερός και είναι αυτό που έχει στο χέρι του ο πελάτης.

Δικαιώματα και όρια

Κάθε κλειδί έχει read, write ή και τα δύο. Οι διαδρομές που αλλάζουν υπόλοιπα απαιτούν write. Ζήτα το ελάχιστο που χρειάζεσαι — ένα κλειδί μόνο ανάγνωσης που διαρρέει δεν μπορεί να χαρίσει πόντους.

Το όριο είναι 600 κλήσεις ανά λεπτό ανά κλειδί. Μετράει το κλειδί και όχι η IP, ώστε δύο καταστήματα πίσω από τον ίδιο πάροχο να μην κλείνουν το ένα το άλλο.

Σφάλματα

Κάθε σφάλμα έχει το ίδιο σχήμα, ό,τι κι αν πήγε στραβά:

{ "error": { "code": "not_found", "message": "Δεν βρέθηκε μέλος με αυτόν τον κωδικό." } }

Το code είναι για τον κώδικά σου, το message είναι στα ελληνικά και μπορεί να δειχθεί σε άνθρωπο. Αυτά ισχύουν σε όλες τις διαδρομές:

HTTPcodeΤι σημαίνει
401no_keyΛείπει η κεφαλίδα Authorization.
401bad_keyΤο κλειδί δεν υπάρχει ή ανακλήθηκε. Ένα μήνυμα και για τα δύο, επίτηδες.
403suspendedΟ λογαριασμός του καταστήματος δεν είναι ενεργός.
403plan_requiredΤο API απαιτεί πακέτο Enterprise.
403scope_requiredΤο κλειδί δεν έχει το δικαίωμα που χρειάζεται η διαδρομή.
400invalid_bodyΤο σώμα δεν είναι έγκυρο· το μήνυμα λέει τι.
429rate_limitedΠάνω από 600 κλήσεις στο λεπτό για αυτό το κλειδί.
500server_errorΔικό μας σφάλμα. Ξαναδοκίμασε.

Διαδρομές

GET/api/v1/customersread

Λίστα και αναζήτηση μελών

Επιστρέφει μέλη ταξινομημένα από το πιο πρόσφατο. Το «q» ψάχνει σε όνομα, email και τηλέφωνο.

ΠαράμετροςΤύποςΠεριγραφή
qstringΌρος αναζήτησης.
limitnumberΠόσα το πολύ. Προεπιλογή 50, ανώτατο 200.

* υποχρεωτικό

Παράδειγμα

curl "https://membly.gr/api/v1/customers?q=παπα&limit=10" \
  -H "Authorization: Bearer mbl_live_..."

Απάντηση

{
  "data": [
    {
      "serial": "GMGV-MMD6-PEZ0",
      "firstName": "Ιωάννα",
      "lastName": "Λαμπρόπουλος",
      "email": "ioanna@example.com",
      "phone": "6910001507",
      "points": 162,
      "lifetimePoints": 162,
      "stamps": 3,
      "tier": "Χάλκινο",
      "createdAt": "2026-08-14T10:00:00.000Z"
    }
  ]
}
GET/api/v1/customers/{serial}read

Ένα μέλος με το ιστορικό του

Το ίδιο αντικείμενο με τη λίστα, συν τις 20 τελευταίες κινήσεις. Ο «serial» είναι ο κωδικός που είναι τυπωμένος στην κάρτα ως barcode.

Παράδειγμα

curl "https://membly.gr/api/v1/customers/GMGV-MMD6-PEZ0" \
  -H "Authorization: Bearer mbl_live_..."

Απάντηση

{
  "data": {
    "serial": "GMGV-MMD6-PEZ0",
    "firstName": "Ιωάννα",
    "points": 162,
    "stamps": 3,
    "tier": "Χάλκινο",
    "transactions": [
      {
        "id": 59,
        "points": 12,
        "amountEur": 12.5,
        "note": "Μέσω API",
        "createdAt": "2026-08-24T11:02:14.881Z"
      }
    ]
  }
}

Σφάλματα αυτής της διαδρομής

  • ·404 not_found — δεν υπάρχει μέλος με αυτόν τον κωδικό
POST/api/v1/customerswrite

Νέο μέλος

Γράφει μέλος και εκδίδει κάρτα. Δεν στέλνεται email επιβεβαίωσης: η εγγραφή έγινε στο δικό σου σύστημα, με τη δική σου συγκατάθεση.

ΠεδίοΤύποςΠεριγραφή
firstName*stringΌνομα.
lastName*stringΕπώνυμο.
emailstringEmail. Χρειάζεται email ή τηλέφωνο — τουλάχιστον ένα.
phonestringΤηλέφωνο.
isStudentbooleanΦοιτητής, για τον αντίστοιχο πολλαπλασιαστή.

* υποχρεωτικό

Παράδειγμα

curl -X POST https://membly.gr/api/v1/customers \
  -H "Authorization: Bearer mbl_live_..." \
  -H "Content-Type: application/json" \
  -d '{"firstName": "Νίκος", "lastName": "Παπαδόπουλος"}'

Απάντηση

201 Created

{
  "data": {
    "serial": "K7TQ-84MN-RB2X",
    "firstName": "Νίκος",
    "lastName": "Παπαδόπουλος",
    "points": 0,
    "stamps": 0,
    "tier": "Χάλκινο",
    "createdAt": "2026-08-24T14:31:02.104Z"
  }
}

Σφάλματα αυτής της διαδρομής

  • ·400 no_contact — δεν δόθηκε ούτε email ούτε τηλέφωνο
  • ·409 already_exists — υπάρχει ήδη μέλος με αυτό το email ή τηλέφωνο. Στο τηλέφωνο επιστρέφεται και το υπάρχον μέλος στο «data», ώστε να συνεχίσεις με τον κωδικό του
POST/api/v1/pointswrite

Πόντοι — πίστωση ή χρέωση

Θετικός αριθμός πιστώνει και περνά από τους πολλαπλασιαστές (φοιτητικό, επίπεδο μέλους, ενεργή προσφορά) — γι' αυτό το «awarded» μπορεί να διαφέρει από αυτό που έστειλες. Αρνητικός χρεώνει χωρίς να αγγίξει τους σωρευτικούς πόντους.

ΠεδίοΤύποςΠεριγραφή
serial*stringΟ κωδικός της κάρτας.
points*numberΑκέραιος, θετικός ή αρνητικός. Το μηδέν απορρίπτεται.
amountEurnumberΤο ποσό της αγοράς, για τις αναφορές.
notestringΣημείωση στην κίνηση.
idempotencyKeystring8–80 χαρακτήρες. Το ίδιο κλειδί δεύτερη φορά **δεν** ξαναδίνει πόντους — επιστρέφει την αρχική κίνηση με «replayed: true». Στείλ' το πάντα από ταμείο.

* υποχρεωτικό

Παράδειγμα

curl -X POST https://membly.gr/api/v1/points \
  -H "Authorization: Bearer mbl_live_..." \
  -H "Content-Type: application/json" \
  -d '{"serial": "GMGV-MMD6-PEZ0", "points": 12, "idempotencyKey": "pos-2026-08-24-0042"}'

Απάντηση

{
  "data": {
    "customer": { "serial": "GMGV-MMD6-PEZ0", "points": 174, "…": "…" },
    "awarded": 12,
    "multiplier": 1,
    "replayed": false,
    "transaction": {
      "id": 59,
      "points": 12,
      "amountEur": 12.5,
      "note": "Μέσω API",
      "createdAt": "2026-08-24T11:02:14.881Z"
    }
  }
}

Σφάλματα αυτής της διαδρομής

  • ·404 not_found — δεν υπάρχει μέλος με αυτόν τον κωδικό
  • ·422 rejected — η κίνηση απορρίφθηκε· το μήνυμα εξηγεί γιατί (π.χ. το υπόλοιπο δεν μπορεί να γίνει αρνητικό)
POST/api/v1/stampswrite

Σφραγίδα

Βάζει σφραγίδες. Όταν γεμίσει η κάρτα, το κουπόνι εκδίδεται μόνο του και ο εξαψήφιος κωδικός επιστρέφεται στο «giftCode».

ΠεδίοΤύποςΠεριγραφή
serial*stringΟ κωδικός της κάρτας.
countnumber1 έως 10. Προεπιλογή 1.

* υποχρεωτικό

Παράδειγμα

curl -X POST https://membly.gr/api/v1/stamps \
  -H "Authorization: Bearer mbl_live_..." \
  -H "Content-Type: application/json" \
  -d '{"serial": "GMGV-MMD6-PEZ0"}'

Απάντηση

{
  "data": {
    "customer": { "serial": "GMGV-MMD6-PEZ0", "stamps": 6, "…": "…" },
    "stamps": 6,
    "left": 0,
    "completed": 1,
    "giftCode": "418302",
    "message": "Γέμισε η κάρτα! Εκδόθηκε κουπόνι με κωδικό 418302."
  }
}

Σφάλματα αυτής της διαδρομής

  • ·404 not_found — δεν υπάρχει μέλος με αυτόν τον κωδικό
  • ·409 stamps_disabled — η κάρτα σφραγίδων δεν είναι ενεργή στις ρυθμίσεις
GET/api/v1/productsread

Κατάλογος

Τα ενεργά προϊόντα με κανονική τιμή και τιμή μέλους. Το «minTier» είναι το ελάχιστο επίπεδο μέλους, ή null για όλους.

Παράδειγμα

curl "https://membly.gr/api/v1/products" \
  -H "Authorization: Bearer mbl_live_..."

Απάντηση

{
  "data": [
    {
      "id": 1,
      "name": "Ελληνική λογοτεχνία",
      "priceEur": 14.9,
      "memberPriceEur": 13.4,
      "category": "Βιβλία",
      "minTier": null
    }
  ]
}

Webhooks

Αντί να μας ρωτάς τι έγινε, σου το λέμε. Ο ιδιοκτήτης δηλώνει διεύθυνση https και διαλέγει συμβάντα· εμείς κάνουμε POST με σώμα:

{
  "event": "points.earned",
  "createdAt": "2026-08-24T11:02:14.881Z",
  "data": { … }
}

Τα συμβάντα

customer.created

Νέο μέλος — από το ταμείο, τη δημόσια φόρμα, εισαγωγή CSV ή το API.

{ "serial": "K7TQ-84MN-RB2X", "firstName": "Νίκος", "lastName": "Παπαδόπουλος",
  "email": "nikos@example.com", "phone": "6941234567",
  "createdAt": "2026-08-24T14:31:02.104Z" }

points.earned

Πιστώθηκαν πόντοι — ταμείο, σάρωση απόδειξης, ουρά offline ή API.

{ "serial": "GMGV-MMD6-PEZ0", "points": 12, "balance": 174,
  "amountEur": 12.5, "note": "Αγορά", "transactionId": 59 }

points.spent

Χρεώθηκαν πόντοι με διόρθωση. Οι εξαργυρώσεις κουπονιών έχουν δικό τους συμβάν.

{ "serial": "GMGV-MMD6-PEZ0", "points": -50, "balance": 124,
  "amountEur": null, "note": "Διόρθωση", "transactionId": null }

stamp.added

Μπήκε σφραγίδα.

{ "serial": "GMGV-MMD6-PEZ0", "stamps": 4, "left": 2, "goal": 6 }

stamps.completed

Γέμισε η κάρτα σφραγίδων. Έρχεται **μαζί** με το stamp.added της ίδιας σφραγίδας.

{ "serial": "GMGV-MMD6-PEZ0", "goal": 6, "giftCode": "418302", "cards": 1 }

coupon.redeemed

Εξαργυρώθηκε κουπόνι — με πόντους ή ως δώρο.

{ "serial": "GMGV-MMD6-PEZ0", "coupon": "Δωρεάν καφές", "pointsSpent": 100,
  "valueEur": 3.5, "balance": 74, "transactionId": 61 }

Επαλήθευση υπογραφής

Κάθε παράδοση φέρει X-Membly-Signature ως t=<δευτερόλεπτα>,v1=<υπογραφή>, όπου η υπογραφή είναι HMAC-SHA256 του "t.σώμα" με το μυστικό του endpoint.

Υπολόγισέ τη πάνω στο ακατέργαστο σώμα, πριν οποιοδήποτε parse: έναJSON.parse και ξανά stringify αλλάζει κενά και σειρά κλειδιών, και η υπογραφή δεν ταιριάζει ποτέ.

const [t, v1] = signature.split(",").map((part) => part.split("=")[1]);
const expected = crypto.createHmac("sha256", secret)
  .update(`${t}.${rawBody}`).digest("hex");

// Σύγκριση σταθερού χρόνου, όχι `===`.
const ok = crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));

// Απόρριψε ό,τι είναι παλιότερο από πέντε λεπτά, αλλιώς μια παλιά
// παράδοση που κατέγραψε κάποιος ξαναπερνά τον έλεγχο.
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;

Επαναλήψεις

Απάντησε 2xx, και γρήγορα: κόβουμε στα 10 δευτερόλεπτα. Αν έχεις δουλειά να κάνεις, βάλ' τη σε δική σου ουρά και απάντησε αμέσως.

Ό,τι δεν είναι 2xx ξαναδοκιμάζεται έξι φορές: μετά από 1 λεπτό, 5, 30, 2 ώρες, 6 και 24. Μετά κλείνει με το σφάλμα καταγεγραμμένο. Ύστερα από 20 συνεχόμενες αποτυχίες ο παραλήπτης μπαίνει σε παύση και ο ιδιοκτήτης τον βλέπει σβηστό, με το σφάλμα δίπλα.

Οι παραδόσεις φέρουν X-Membly-Delivery με σταθερό αναγνωριστικό. Μια επανάληψη έχει το ίδιο: κράτα τα όσα είδες, ώστε μια διπλή παράδοση να μη γίνει διπλή ενέργεια.

API & webhooks · Membly