Skip to main content

Webhooks

Platzi can notify your systems the moment one of your employees finishes a certifying evaluation, so your compliance records stay in sync without polling our API.

Webhooks are outbound: you do not call an endpoint, you expose one. Platzi sends an HTTP POST to a URL you own every time a qualifying event happens.

The flow

  1. You configure it. A company administrator sets the destination URL in the Business dashboard — see Getting set up.
  2. An employee finishes a certifying evaluation on Platzi: a course exam or a learning-path exam.
  3. Platzi builds the event with the employee, what was evaluated and the outcome.
  4. Platzi POSTs it to your URL, over HTTPS, with your token attached.
  5. Your endpoint answers 2xx. If it does not, Platzi retries on a fixed schedule.

Every attempt is recorded on our side, with the status code and the first 500 characters your endpoint returned — enough to diagnose most rejections without opening a support ticket.

What produces an event

Only certifying evaluations. Practice quizzes and diagnostic assessments run through the same exam interface but grant no certification, so they never produce an event. If they did, you would be recording certifications Platzi never issued.

Getting set up

You configure this yourself, in the Business dashboard under Conexiones. No ticket, no waiting on us.

It is an administrator-only screen. Group managers do not see it, because the endpoint belongs to the company as a whole rather than to any one team.

  1. Enter the endpoint URL that will receive the POST.
  2. Save.

That is the whole setup. On that first save Platzi generates your auth token and shows it under the URL, masked. Reveal it, copy it, and teach your endpoint to require it before you point real traffic at this.

From then on the screen shows your URL, your token and a switch to pause delivery. Changes take effect on the next event — nothing to redeploy on either side.

Use the pause switch sparingly: it keeps your URL and token, but events that happen while it is off are not queued and not recorded. They are simply never sent, and there is nothing to recover afterwards.

The URL

RequirementWhy
https://, never http://The payload carries employee names, emails and whatever custom attributes you configured. It does not travel in the clear.
Reachable from the public internetPlatzi calls you from outside your network. An address that only resolves inside your VPN cannot be delivered to.
Valid, complete certificate chainA self-signed or incomplete chain fails the handshake, and the event is retried until it gives up.
Port 443 if you spell one outMost URLs need no port at all — HTTPS already means 443. A URL with an explicit port other than 443 is rejected.
A final URL, not a redirectRedirects are not followed. Give us the address that actually answers.

Editing the URL later is safe: your token stays the same, so your endpoint keeps accepting the calls without any change on your side.

The token

Platzi generates it — you do not choose it. It is a long random string starting with pltz_whk_, and it arrives on every request:

POST /your/endpoint HTTP/1.1
Authorization: Bearer pltz_whk_XxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx
User-Agent: Platzi-Webhooks/1.0
X-Platzi-Event-Id: 9f8e7d6c-5b4a-3210-9876-543210fedcba
Content-Type: application/json

Your endpoint's job is to compare that header against the value in your dashboard and refuse anything that does not match. Use a constant-time comparison — hmac.compare_digest in Python, crypto.timingSafeEqual in Node — rather than ==, so the comparison itself does not leak the token one character at a time.

It is not a show-once secret. It stays visible in the dashboard, so losing your copy is not a lockout: open the screen, reveal it, copy it again.

Rejecting drops the event. A 401 is a 4xx, and a 4xx is never retried — see Retries. So if your endpoint answers 401 because your side has the wrong value stored, the certification is not queued for later, it is gone until someone re-sends it by hand. Deploy the token on your side before you rely on it, not after.

Changing it

The dashboard does not rotate the token, on purpose: there is no button that can invalidate a working integration by accident. To change it, ask your account manager.

Plan for a brief gap when you do. There is no window where both the old and the new token are accepted, so any event landing between our change and your deploy gets a 401.

Do not use the pause switch to cover that gap. A 401 is at least recorded: the delivery exists on our side and your account manager can re-send it once you are ready. An event that happens while delivery is paused is not recorded at all — there is nothing to re-send, because as far as the integration is concerned it never had a destination. Take the 401s and ask for a re-send; do not take the silence.

If your webhook predates this screen

Whatever token you chose back then is kept. The dashboard shows it and does not replace it, so nothing about your integration changes.

The one exception is a webhook that was configured with no token at all. The first time anyone saves from this screen, one is generated, and your endpoint starts receiving an Authorization header where it previously received none. Ignoring an unexpected header is harmless — but it is the moment to start checking it.

Why you want a token

Your endpoint is on the public internet. Without a token, anything that discovers the URL can POST a fabricated certification into your compliance system, and you have no way to tell it apart from a real one. A URL is not a secret: it leaks into proxy logs, load balancer logs, browser history and error reports.

The token turns that around — reject any request whose Authorization header does not match, and forged calls stop at your front door.

Two things to be clear about, so it is used correctly:

  • Treat it like a password. Copy it from the dashboard into the same secret store you keep your other credentials in — not into a repository, a ticket or a chat thread.
  • It authenticates the caller, not the body. This is a shared secret sent over TLS, not a per-request cryptographic signature. It proves the request came from someone who knows the token; it does not independently prove the payload was untampered.

Event types

event_type reads as <what was evaluated>.<outcome>.

event_typeMeaning
course.certifiedPassed a course exam. The certification was granted.
course.failedTook a course exam and did not pass.
course.expiredRan out of time on a course exam.
learning_path.certifiedPassed a learning-path exam.
learning_path.failedTook a learning-path exam and did not pass.
learning_path.expiredRan out of time on a learning-path exam.

An exam the employee opened and simply walked away from does not produce an event. Only a finished attempt — passed, failed, or timed out — counts.

New event types may be added over time. Ignore the ones you do not recognise rather than failing the request.

Payload

{
"transaction_id": "9f8e7d6c-5b4a-3210-9876-543210fedcba",
"event_type": "course.certified",
"target_system": null,
"occurred_at": "2026-08-04T15:04:05+00:00",
"user": {
"id": 123456,
"email": "ana@acme.com",
"name": "Ana Díaz",
"metadata": { "rfc": "ABC123456XYZ", "numero_asesor": "7788" }
},
"company_id": 8488,
"course": {
"id": 4321,
"name": "Curso de Cumplimiento Normativo",
"learning_path_id": null
},
"result": {
"status": "approved",
"score": 9.5,
"score_scale": 10,
"approved": true,
"certification_obtained": true,
"completed_at": "2026-08-04T15:04:05+00:00"
}
}

Fields

FieldDescription
transaction_idUnique id for this delivery. Same value as the X-Platzi-Event-Id header. Use it to reconcile on your side.
event_typeSee Event types.
target_systemnull on the standard contract. Only set if Platzi built a custom payload format for a specific system of yours.
occurred_atWhen the evaluation ended. ISO-8601 with timezone.
user.idThe employee's Platzi user id. Stable across events.
user.emailThe email the employee uses on Platzi.
user.metadataThe custom employee attributes your company configured (for example rfc, numero_asesor). Only the fields you defined are sent, exactly as you defined them — this is usually your join key back to your own HR records.
company_idYour Platzi company id. Relevant if you route more than one company into the same endpoint.
course.id / course.nameThe course that was evaluated. Set on course.* events, null on learning_path.* events.
course.learning_path_idThe learning path that was evaluated. Set on learning_path.* events, null otherwise.
result.statusapproved (passed), failed (took it and did not pass), expired (ran out of time).
result.scoreThe grade obtained. Meaningless without score_scale — see below.
result.score_scaleThe maximum possible score. 10 means score is out of 10.
result.approvedWhether the attempt was a pass.
result.certification_obtainedWhether the employee actually earned the certification. This is the field to write into a compliance record.
result.completed_atWhen the attempt was completed.

score is not a percentage. Platzi grades on a 0–10 scale, so a 9.5 is 95%, not 9.5%. Always read score together with score_scale and convert explicitly. Assuming a 0–100 scale is the most common integration bug here.

Fields may be added to the payload over time. Ignore the ones you do not recognise rather than failing the request.

Headers

HeaderDescription
Content-TypeAlways application/json.
User-AgentPlatzi-Webhooks/1.0.
X-Platzi-Event-IdStable id for this event. Identical across retries — this is what you deduplicate on.
X-Platzi-Event-TypeSame value as event_type in the body. Lets you route without parsing the body.
AuthorizationBearer <token> — the token shown in your dashboard.

What your endpoint should validate

On every request, in this order:

  1. The token. Compare the Authorization header against the token shown in your dashboard, using a constant-time comparison. Reject with 401 if it does not match.
  2. Whether you have seen X-Platzi-Event-Id before. Delivery is at-least-once: a network hiccup after your 2xx looks identical to a failure from our side, so the same event can arrive more than once. Deduplicate on this id and answer 2xx to the repeat.
  3. event_type. Handle the types you care about; ignore unknown ones with a 2xx. Returning an error for an unrecognised type only fills your retry queue.
  4. company_id, if a single endpoint serves several Platzi companies.
  5. Which object was evaluated. On learning_path.* events course.id and course.name are null — read course.learning_path_id instead. Code that assumes course.id is always present will break the first time an employee takes a learning-path exam.
  6. result.score against result.score_scale before storing or comparing it to a passing threshold.
  7. occurred_at, not arrival time. Retries mean events can arrive out of order. If you keep a per-employee latest state, order by occurred_at.

Then:

  • Answer 2xx within 10 seconds. Acknowledge first and do your processing asynchronously. A slow endpoint is treated exactly like a broken one.
  • Do not rely on redirects. They are not followed. Give us the final URL.

Retries

Your responseWhat happens
2xxDelivered. Done.
5xx, timeout, connection errorRetried after 30s, 2m, 10m, 1h, 6h — 6 attempts in total.
429, 408Retried, same schedule.
Any other 4xxNot retried. A 4xx means the request itself was rejected, and sending the same bytes again cannot fix that.

That last row is worth internalising: if your endpoint answers 400 or 403 because of a bug, a bad deploy or an expired token, the event is dropped after the first attempt. It is still stored on our side and can be re-sent manually, but nothing happens automatically. When in doubt, answer 503 and let the retry schedule work.

After the 6 attempts are exhausted the event is marked as failed and kept for manual re-sending.

When something goes wrong

Every attempt — successful or not — is logged on the Platzi side with the timestamp, the status code your endpoint returned, and a snippet of its response body. Your account manager can, on request:

  • Send a test event with fictitious data to your endpoint, through the exact same path a real event takes. Use this to validate a new URL, a token change or a certificate renewal before real traffic depends on it.
  • Pull the delivery log for a date range, so you can reconcile against what your side actually received.
  • Re-send a failed event, once you have fixed the outage, the certificate or the URL.

Common causes, in rough order of frequency:

SymptomUsual cause
Nothing ever arrivesThe evaluation was not a certifying one, or the configuration is not active yet.
401 / 403 in the logToken mismatch — your endpoint is checking against an older value than the one in the dashboard.
TimeoutsThe endpoint processes synchronously and exceeds 10 seconds.
TLS errors in the logExpired, self-signed, or incomplete certificate chain.
Duplicated records on your sideMissing deduplication on X-Platzi-Event-Id.