AWRA OpsHub Search

The Password That Is Different Every Time

The password on an M-Pesa request is not stored anywhere. It is built fresh for every call out of three pieces — one of which is the current time — and sent alongside the timestamp it was built from, so the two have to agree exactly.

Integrations & Data AWRA OpsHub Team 11 min read

Most APIs hand you a credential and you send it. Daraja, the interface behind M-Pesa, does something different for the payment request itself: there is no password to store, because the password is computed at the moment of the call from your identifiers and the clock. It is a small piece of cryptographic housekeeping with a real consequence, and the consequence is that your server's clock is now part of your payment infrastructure.

Three ingredients, joined and encoded

The password field on a payment request is built by joining three things and encoding the result: the short code the money is going to, a secret key issued for that short code, and the current time formatted to the second.

And the same timestamp is sent as its own field beside it.

That is the whole mechanism, and the design intent is visible once you see the two fields together. The recipient can rebuild the password from the timestamp you gave them and the secret they already hold. If it matches, you knew the secret. If the timestamp is far from now, the request is stale.

The secret itself never travels. What travels is proof you had it, stamped with when.

The secret never travels. What travels is proof you had it, stamped with when.
The secret never travels. What travels is proof you had it, stamped with when.

What this means for your clock

It means a server whose time has drifted produces requests that are rejected, and the rejection has nothing in it about time.

The password will not match — because the far end rebuilds it from the timestamp you sent, and yours was computed from the same wrong clock, so it is internally consistent and externally wrong. What comes back is an authentication failure.

Which sends everybody to the credentials. New keys, re-paste, try again, same result. Nothing in the error mentions the clock, and the clock is not on anybody's list when payments stop working.

On a managed server this is close to a non-issue, because time synchronisation is standard. It is worth knowing anyway, because it is the second thing to check after the credentials and almost nobody gets to it in under an hour.

A second credential, cached and separated

Alongside the computed password there is an ordinary access token, obtained from your consumer key and secret and good for about an hour.

It is cached for slightly less than its life, so it is renewed before it expires rather than discovered as expired by a payment request. That margin is the same instinct as the storage connectors renewing a minute before expiry: a credential should never run out during an operation, only between them.

The cache key includes both the workspace and the environment. That second part matters more than it looks.

Without the environment in the key With it
A sandbox token could serve a production request They are separate entries and cannot cross
Switching environment reuses the old token Switching gets its own token immediately
The failure is an authentication error with no clue The question does not arise

Testing and live payments sharing a cache entry is the kind of bug that only appears when somebody switches an environment, which is exactly when nobody wants a mystery.

Where it is deliberately not cached

One endpoint is treated differently, and it is worth describing because it is an honest accommodation of somebody else's unreliability rather than a design.

The endpoint that produces a scannable payment code rejects tokens that work perfectly well elsewhere — the same token, accepted by the payment endpoint, refused by this one. So that call retries, minting a fresh token each time rather than reusing the cached one.

It is a workaround. It is written down as a workaround in a comment beside the retry, with the observed behaviour it is compensating for, because the alternative is somebody removing it in six months as redundant and rediscovering the problem from the other end.

And the error message this path produces, in the test environment, says that the sandbox is known to be unreliable here and suggests trying again. That is a better message than the provider's, because it carries something the provider's cannot: the knowledge that this specific failure is usually not your fault.

The refusals that come first

Three checks run before any of this, each producing a message that names its own cause.

  1. Is the connector fully configured?

    A missing value produces a message saying so, rather than a request that fails somewhere inside Daraja with a code.

  2. Is the phone number usable?

    Normalised into the form the network expects and refused if it cannot be. The reason names the number.

  3. Did the token come back?

    A failure here says to check the consumer key and secret, because that is what it means — as distinct from a payment being refused.

The value of three separate messages over one is the same everywhere in this series. Payment failed is true and useless. Check the consumer key and secret is one sentence and one fix.

What is in place, layer by layer

The M-Pesa credential path

A password computed per request

Built at the moment of the call from your short code, your key and the current second, so the secret itself never travels.

Built in

The timestamp sent with it

The same value the password was built from, so the far end can rebuild and compare rather than store what you sent.

Built in

A token cached below its lifetime

Renewed before it expires rather than discovered as expired by a payment, so a credential never runs out mid-operation.

Built in

Test and live tokens kept apart

The cache key carries the environment, so a sandbox token can never serve a live request and switching takes effect at once.

Built in

A forced refresh where it is needed

One endpoint rejects tokens that work elsewhere, so that path mints a fresh one per attempt rather than reusing the cache.

Built in

The workaround documented as one

The retry carries a note saying what it compensates for, so it is not removed later as redundant by somebody who never saw the failure.

Built in

A better message than the provider's

Where a failure is known to be the test environment misbehaving, the message says so and suggests trying again.

Built in

Three named refusals before the call

Incomplete configuration, an unusable phone number and a failed token each produce their own message naming their own fix.

Built in

Your own Daraja credentials

Your short code, your keys, your till. Money moves between your customer and your account.

Built in

Because the password is built from the current second, the server's clock is part of the payment path. Drift produces an authentication failure with nothing in it about time.

Three positions held on purpose

  • Test and live credentials are separated by the cache key rather than by care. A shared entry would only cause trouble when somebody switches environment, which is precisely the moment nobody wants a mystery.
  • A workaround for somebody else's unreliability is written down as one. An undocumented retry looks like belt and braces and gets removed by the next person to read it, who then rediscovers the problem from the failure end.
  • Each refusal names its own cause. A single failure message covering configuration, a bad number and a rejected credential is accurate and costs whoever is fixing it the hour it would have saved them.

Five questions about a payment integration

Where is the payment password stored?

A good answer sounds like

It is not.

What ours actually is

Computed per request from your short code, your key and the current second.

Does the server clock matter?

A good answer sounds like

Yes — and they know.

What ours actually is

The timestamp is part of the credential, so drift produces an authentication failure that says nothing about time.

Can a sandbox credential reach production?

A good answer sounds like

No.

What ours actually is

The token cache is keyed by environment as well as workspace, so the two can never cross.

What if the token expires mid-payment?

A good answer sounds like

It cannot.

What ours actually is

Cached below its lifetime and renewed before use rather than discovered as expired.

Whose till does the money reach?

A good answer sounds like

Yours.

What ours actually is

Your short code and your credentials. Funds move between your customer and your account.

Our take

Time-bound credentials are a good idea and they quietly recruit your infrastructure into the security model. The moment a password contains a timestamp, clock synchronisation stops being an operational nicety and becomes a dependency of getting paid — and it is a dependency that fails in the least helpful way available, as an authentication error. If you run anything that signs requests with a timestamp, the useful habit is not to worry about it but to write it on the list: credentials, then the clock, then everything else. It takes thirty seconds to rule out and it is otherwise the last thing anybody thinks of.

Collect payments into your own till

Your short code, your Daraja credentials, your account. The connector moves the request; the money never passes through us.

Talk through M-Pesa collection

Frequently asked questions

Do we give you our M-Pesa passkey?

It is stored against your workspace and used to compute the password on each request, encrypted at rest and never written into a log line. The passkey itself never travels to Safaricom — what goes out is a value computed from it, which is the point of the arrangement.

Payments suddenly stopped and the credentials are unchanged. What should we check?

The server clock, after the credentials. The password contains a timestamp, so drift produces a mismatch that is reported as an authentication failure with nothing in it about time. It is a thirty-second check and it is almost never on anybody's list.

Can we test in the sandbox without risking live payments?

Yes. The environment is part of the configuration and part of the token cache key, so sandbox and production credentials cannot cross. Switching environment takes effect immediately rather than reusing whatever was cached.

Why does the payment QR sometimes fail when payments work?

That endpoint rejects tokens that the payment endpoint accepts, particularly in the test environment. The connector retries with a fresh credential each attempt, and where it still fails the message says the sandbox is known to be unreliable here rather than implying your setup is wrong.

Does money ever pass through your account?

No. The connector uses your own credentials against your own short code, so funds move between your customer and your till. What the connector does is make the request and record the outcome.

Help Center

Need a quick answer while you read?

Run inventory, procurement, assets, sales, and field work with approved AWRA guidance for setup, migration, integrations, security, pricing, and support.

Search all approved AWRA public help articles.

Open Help Center