Giuliano Bentevenga

Argentina — open to remote

> backend engineer_

Backend Lead with 4+ years in production. I've built municipal identity systems and transactional booking platforms from scratch, focusing on scalability, security, and reliability that holds up in the real world.

Specialized in payments (Mercado Pago, Stripe, Pix), identity (ARCA, ANSES, RENAPER, Mi Argentina), and third-party integrations (Google, Meta, Amazon SES). I design systems that stay correct under concurrency and scale as the business grows.

GitHubLinkedIn
download started

Thanks for downloading the resume!

Two systems I built and lead in production. Open a project for the full technical breakdown — ADRs, scale constraints, and failure modes.

Production project

Transactional Booking & Payment Platform

Django · PostgreSQL · payments & catalog integrations

View live site

Booking platform for an operator with 180k+ passengers/year — built from scratch and running in production. The core constraint: a reservation is only 'paid' when the webhook says so, never based on client state. Webhooks are the single source of truth, validated with HMAC and processed idempotently by event_id. Availability is locked pessimistically (SELECT FOR UPDATE) so concurrent bookings on the same slot serialize rather than race.

Patagonia Dreams — booking platform

  • 01
    Context

    Concurrent reservation requests for the same slot both read 'available' before either committed — a classic read-modify-write race that caused double bookings on high-demand activities.

    What I did

    Added SELECT FOR UPDATE on the availability row inside the same transaction that creates the reservation. The second request blocks until the first commits or rolls back, then sees updated state and either succeeds or fails cleanly.

    Impact

    Double bookings eliminated at the DB boundary. No application-level lock, no retry logic — the database is the serialisation point and the behaviour is deterministic.

  • 02
    Context

    Payment flow initially relied on the provider redirect callback to mark a reservation as 'paid'. On real traffic, tab closures, provider retries, and network drops meant the callback arrived late, twice, or not at all.

    What I did

    Moved 'reservation paid' to be set exclusively by the webhook handler — never by the redirect. HMAC validation on every incoming payload; idempotent processing by event_id inside a single DB transaction.

    Impact

    Payment state became consistent regardless of client behaviour. Duplicate webhooks are safe no-ops. The redirect is now UI-only; it never drives state.

  • 03
    Context

    Cognito confidential app clients require a SECRET_HASH parameter in several auth calls (sign_up, confirm_sign_up, authenticate, refresh_token). Missing it on any call returns a cryptic error that is not obvious from the AWS docs.

    What I did

    Audited every Cognito SDK call site, added SECRET_HASH derived from HMAC-SHA256(username + client_id, client_secret) consistently, and centralised the computation so future calls cannot skip it.

    Impact

    Auth flows stable in production with no SECRET_HASH errors. New developers can't accidentally omit it — the helper enforces it.

  • 04
    Context

    Business rules needed identifiers scoped by activity: global mapping let the same modality leak across unrelated activities.

    What I did

    Moved to scope-aware mapping, pushed critical uniqueness into the database with conditional constraints where legacy data still had to live, and used additive migration plus explicit legacy fallback order.

    Impact

    Fewer cross-activity data bugs and a defensible story under concurrency — validators are not enough if the DB cannot enforce the rule.

  • 05
    Context

    The backoffice export used CSV. Booking data entered by users contained strings starting with =, +, or - that Excel auto-executed as formulas — a classic injection vector with real commercial data at risk.

    What I did

    Replaced the CSV endpoint with a JSON response. Removed the sanitisation problem entirely by eliminating the format, not by patching it.

    Impact

    Injection vector gone. Operations staff get structured data with full fidelity; no formula execution risk regardless of input content.

  • 06
    Context

    Some card payments with Mercado Pago were failing payer validation silently — no explicit error on our side, but customers saw the checkout break. Nothing looked wrong in our logs.

    What I did

    MP expects {area_code: '11', number: '12345678'}, but users submit phone numbers in dozens of formats (+5491112345678, with/without the mobile 9, no country prefix, etc.). Built a normaliser that strips non-digits, removes the 54 country code, strips the mobile 9 where applicable, then splits into area_code + number. If the result has fewer than 8 total digits, it sends null and omits the field entirely rather than passing garbage.

    Impact

    Silent failure mode closed. The phone field now only travels when valid; MP stops rejecting payers over format. Fix required reading the undocumented edge cases in MP's payer spec, not the happy path.

  • 07
    Context

    When a group of reservations was paid in instalments or with two payment methods, the second webhook arrived approved but the system marked it as 'partial payment' and didn't release the reservation. Operations had to intervene manually every time.

    What I did

    The original flow called GET /v1/payments/search inside transaction.atomic() to sum all approved payments with the same external_reference. The Search API has eventual consistency — a payment_id that just approved may not appear in search results for a few seconds. Moved the Search call before the lock; added an explicit guard: if payment_id is missing from search results, add the verified amount manually (the payment was already confirmed via GET /payments/{id} which is consistent). Also added a fast-path: if the current payment alone covers the expected total, approve without calling Search.

    Impact

    False rejections on split payments eliminated. Fast-path reduced Search API calls in the most common single-payment case. Fix required reading MP's eventual consistency docs buried outside the main webhook guide.

  • 08
    Context

    Flash deals and coupons with past expiry dates kept appearing as available at checkout. The team loaded them with an expiry date and expected the system to deactivate them automatically — but nothing did.

    What I did

    No Celery, no cron. The solution was write-on-read: run check_and_update_expired_discounts() inside the read path — every time the promotions or coupons lists are fetched, an UPDATE runs on expired records and Redis cache is invalidated if anything changed. Change frequency is low enough that the extra write cost is acceptable without worker infrastructure.

    Impact

    Expired discounts deactivate automatically within minutes of the next read. No manual intervention, no external cron, no infra cost. The pattern trades a small write overhead for complete operational simplicity.

  • 09
    Context

    Activities with a cutoff of 'book by 18:00 the day before' started rejecting reservations from 15:00. Guides reported customers couldn't book during slots that should still be open. The bug only appeared in production, not locally.

    What I did

    The validation compared timezone.localtime() against the cutoff time. The server (Railway) ran in UTC and TIME_ZONE wasn't set to America/Argentina/Buenos_Aires, so localtime() returned UTC — which is UTC-3 relative to Argentina. The cutoff effectively fired 3 hours early. Fixed by setting TIME_ZONE = 'America/Argentina/Buenos_Aires' with USE_TZ = True, and making every datetime comparison use timezone.localtime() explicitly instead of datetime.now().

    Impact

    Booking window aligned with Buenos Aires real time. False rejections in the 15:00–18:00 window eliminated. Classic production-only bug: local dev and Railway were in different timezones and nothing failed loudly.

  • 10
    Context

    On slow connections, the frontend retried the payment preference creation call if it didn't get a response in time. This created two distinct MP preferences for the same booking — customers saw two payment links, and if they paid both, we had an overcharge.

    What I did

    Added an x-idempotency-key to the preference creation request: a SHA-256 hash over the deterministic fields of the payload (booking IDs, amounts, external reference). MP guarantees requests with the same idempotency key within a window return the existing preference without creating a new one. The key is computed once with hashlib.sha256 over the serialised relevant payload.

    Impact

    Double-preference failure mode closed. Frontend retries are idempotent by construction. The fix required no changes to the frontend — just the right key on the server side before calling MP.

  • 11
    Context

    After introducing payment_group_id (UUID) to group multiple bookings under one payment, alerts started firing for MP webhooks that couldn't find any booking. They were old reservations created before the field existed.

    What I did

    The new webhook searched exclusively by payment_group_id. Added backward-compat logic: if no booking is found by payment_group_id, fall back to booking_id (the old path), and if found, backfill payment_group_id on the booking at that point so future webhooks hit the fast path. The code logs 'legacy booking without payment_group_id' explicitly for transition visibility.

    Impact

    Zero orphaned bookings during migration. The field self-fills on the first webhook hit — no mass migration script, no downtime, no manual backfill. The transition period was observable from logs.

Municipal identity gateway

  • 12
    Context

    Many services needed one citizen identity without each system re-calling national registries or duplicating verification.

    What I did

    Central gateway issues tokens; downstream services validate and apply RBAC only. Minimal claims in tokens, fail-safe when national APIs are unavailable, audit on sensitive paths.

    Impact

    One trust boundary instead of many implicit ones; easier reasoning about security and compliance at interview depth.

Payment orchestrator (design)

  • 13
    Context

    Payment initiation and webhooks must stay correct under retries, duplicates, and overlapping writes.

    What I did

    Required idempotency keys for initiations, outbox-style separation for provider calls, webhook handling idempotent by provider event id, tight transaction boundaries per state transition.

    Impact

    Duplicate events and client retries become safe paths instead of incident generators—matches what senior roles expect you to articulate.

Core stack
PythonDjango REST FrameworkPostgreSQL
Working knowledge
AWSCI/CDGitHub ActionsDocker
Integrations
Google OAuthMercado PagoStripePixAmazon SESCognitoMeta
contact.sh